summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java54
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java63
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java68
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java100
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java62
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java415
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java80
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java79
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java338
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java183
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java180
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java28
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java1
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java2
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java167
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java306
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java22
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml (renamed from libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml)14
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml)2
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml)6
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml)8
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml)4
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml26
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml135
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml40
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml125
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings_tv.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml51
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml43
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings_tv.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml45
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml47
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml43
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml49
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml43
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml43
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml95
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-television/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml24
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml39
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml41
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml17
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml9
-rw-r--r--libs/WindowManager/Shell/res/values/strings_tv.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java478
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java516
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java127
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java642
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java410
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java361
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java359
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java129
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java)91
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java91
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java245
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java250
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java98
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java464
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java463
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java280
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java669
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java294
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java759
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java229
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java223
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java508
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java786
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java467
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java165
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java257
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java153
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java577
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java280
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt167
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt457
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt61
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt89
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt)51
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt)17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt)34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt (renamed from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java)22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt140
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt)32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt)19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt)27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt)20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt60
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt74
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt90
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt212
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt211
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt66
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt)83
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt98
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt)51
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt139
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt195
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt220
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt221
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt147
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt60
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt92
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt)66
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt)67
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt96
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt114
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt68
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt73
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt102
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt)74
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt)15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt117
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt73
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt110
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt111
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt68
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt187
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt37
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt150
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt158
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt165
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt166
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt79
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt)37
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt190
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt102
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt151
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt187
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt168
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt162
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt146
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt184
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt143
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt172
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt129
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt376
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt213
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt173
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt186
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt186
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt241
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml147
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.pngbin1966 -> 0 bytes
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml141
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java173
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java108
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java66
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java82
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java308
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java61
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java105
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java50
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java375
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java107
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java328
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java348
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt81
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java148
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java108
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java294
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java189
-rw-r--r--libs/androidfw/Android.bp11
-rw-r--r--[-rwxr-xr-x]libs/androidfw/ApkAssets.cpp13
-rw-r--r--libs/androidfw/ApkParsing.cpp110
-rw-r--r--libs/androidfw/AssetManager2.cpp231
-rw-r--r--libs/androidfw/AssetsProvider.cpp103
-rw-r--r--libs/androidfw/BigBuffer.cpp87
-rw-r--r--libs/androidfw/ConfigDescription.cpp47
-rw-r--r--libs/androidfw/Idmap.cpp76
-rw-r--r--libs/androidfw/LoadedArsc.cpp137
-rw-r--r--libs/androidfw/Locale.cpp6
-rw-r--r--libs/androidfw/PosixUtils.cpp54
-rw-r--r--libs/androidfw/ResourceTimer.cpp271
-rw-r--r--libs/androidfw/ResourceTypes.cpp144
-rw-r--r--libs/androidfw/ResourceUtils.cpp10
-rw-r--r--libs/androidfw/StringPool.cpp507
-rw-r--r--libs/androidfw/TypeWrappers.cpp13
-rw-r--r--libs/androidfw/Util.cpp145
-rw-r--r--libs/androidfw/include/androidfw/ApkParsing.h31
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h20
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h31
-rw-r--r--libs/androidfw/include/androidfw/BigBuffer.h192
-rw-r--r--libs/androidfw/include/androidfw/ByteBucketArray.h58
-rw-r--r--libs/androidfw/include/androidfw/ConfigDescription.h8
-rw-r--r--libs/androidfw/include/androidfw/IDiagnostics.h130
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h29
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h21
-rw-r--r--libs/androidfw/include/androidfw/Locale.h6
-rw-r--r--libs/androidfw/include/androidfw/PosixUtils.h14
-rw-r--r--libs/androidfw/include/androidfw/ResourceTimer.h221
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h184
-rw-r--r--libs/androidfw/include/androidfw/ResourceUtils.h4
-rw-r--r--libs/androidfw/include/androidfw/Source.h90
-rw-r--r--libs/androidfw/include/androidfw/StringPiece.h292
-rw-r--r--libs/androidfw/include/androidfw/StringPool.h228
-rw-r--r--libs/androidfw/include/androidfw/Util.h135
-rw-r--r--libs/androidfw/include/androidfw/misc.h4
-rw-r--r--libs/androidfw/misc.cpp44
-rw-r--r--libs/androidfw/tests/ApkParsing_test.cpp77
-rw-r--r--libs/androidfw/tests/AttributeResolution_bench.cpp4
-rw-r--r--libs/androidfw/tests/BigBuffer_test.cpp101
-rw-r--r--libs/androidfw/tests/ByteBucketArray_test.cpp53
-rw-r--r--libs/androidfw/tests/ConfigDescription_test.cpp24
-rw-r--r--libs/androidfw/tests/Config_test.cpp14
-rw-r--r--libs/androidfw/tests/LoadedArsc_test.cpp136
-rw-r--r--libs/androidfw/tests/PosixUtils_test.cpp18
-rw-r--r--libs/androidfw/tests/ResTable_test.cpp68
-rw-r--r--libs/androidfw/tests/ResourceTimer_test.cpp245
-rw-r--r--libs/androidfw/tests/SparseEntry_bench.cpp67
-rw-r--r--libs/androidfw/tests/StringPiece_test.cpp32
-rw-r--r--libs/androidfw/tests/StringPool_test.cpp388
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp170
-rw-r--r--libs/androidfw/tests/data/overlay/overlay.idmapbin636 -> 732 bytes
-rw-r--r--libs/androidfw/tests/data/sparse/Android.bp23
-rw-r--r--libs/androidfw/tests/data/sparse/AndroidManifest.xml1
-rw-r--r--libs/androidfw/tests/data/sparse/R.h2
-rwxr-xr-xlibs/androidfw/tests/data/sparse/gen_strings.sh10
-rw-r--r--libs/androidfw/tests/data/sparse/not_sparse.apkbin62155 -> 62219 bytes
-rw-r--r--libs/androidfw/tests/data/sparse/res/values-land/strings.xml (renamed from libs/androidfw/tests/data/sparse/res/values-v26/strings.xml)2
-rw-r--r--libs/androidfw/tests/data/sparse/res/values-land/values.xml (renamed from libs/androidfw/tests/data/sparse/res/values-v26/values.xml)0
-rw-r--r--libs/androidfw/tests/data/sparse/sparse.apkbin59459 -> 59523 bytes
-rw-r--r--libs/hwui/Android.bp28
-rw-r--r--libs/hwui/AndroidTest.xml1
-rw-r--r--libs/hwui/CanvasTransform.cpp34
-rw-r--r--libs/hwui/CanvasTransform.h2
-rw-r--r--libs/hwui/ColorMode.h5
-rw-r--r--libs/hwui/CopyRequest.h42
-rw-r--r--libs/hwui/DeferredLayerUpdater.h1
-rw-r--r--libs/hwui/DeviceInfo.cpp14
-rw-r--r--libs/hwui/DeviceInfo.h10
-rw-r--r--libs/hwui/DisplayListOps.in2
-rw-r--r--libs/hwui/FrameInfo.h1
-rw-r--r--libs/hwui/Gainmap.cpp (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt)31
-rw-r--r--libs/hwui/Gainmap.h (renamed from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java)26
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp9
-rw-r--r--libs/hwui/HardwareBitmapUploader.h3
-rw-r--r--libs/hwui/Layer.cpp2
-rw-r--r--libs/hwui/MemoryPolicy.cpp69
-rw-r--r--libs/hwui/MemoryPolicy.h62
-rw-r--r--libs/hwui/Mesh.cpp102
-rw-r--r--libs/hwui/Mesh.h199
-rw-r--r--libs/hwui/Properties.cpp19
-rw-r--r--libs/hwui/Properties.h44
-rw-r--r--libs/hwui/Readback.cpp127
-rw-r--r--libs/hwui/Readback.h20
-rw-r--r--libs/hwui/RecordingCanvas.cpp258
-rw-r--r--libs/hwui/RecordingCanvas.h103
-rw-r--r--libs/hwui/Rect.h9
-rw-r--r--libs/hwui/RenderNode.h1
-rw-r--r--libs/hwui/SafeMath.h107
-rw-r--r--libs/hwui/SkiaCanvas.cpp85
-rw-r--r--libs/hwui/SkiaCanvas.h36
-rw-r--r--libs/hwui/SkiaInterpolator.cpp27
-rw-r--r--libs/hwui/SkiaInterpolator.h12
-rw-r--r--libs/hwui/Tonemapper.cpp118
-rw-r--r--libs/hwui/Tonemapper.h (renamed from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java)22
-rw-r--r--libs/hwui/VectorDrawable.cpp5
-rw-r--r--libs/hwui/VectorDrawable.h1
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp5
-rw-r--r--libs/hwui/apex/android_bitmap.cpp5
-rw-r--r--libs/hwui/apex/android_canvas.cpp2
-rw-r--r--libs/hwui/apex/android_paint.cpp1
-rw-r--r--libs/hwui/apex/jni_runtime.cpp119
-rw-r--r--libs/hwui/canvas/CanvasOps.h12
-rw-r--r--libs/hwui/effects/GainmapRenderer.cpp304
-rw-r--r--libs/hwui/effects/GainmapRenderer.h38
-rw-r--r--libs/hwui/hwui/Bitmap.cpp38
-rw-r--r--libs/hwui/hwui/Bitmap.h14
-rw-r--r--libs/hwui/hwui/BlurDrawLooper.cpp2
-rw-r--r--libs/hwui/hwui/Canvas.cpp1
-rw-r--r--libs/hwui/hwui/Canvas.h17
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp111
-rw-r--r--libs/hwui/hwui/ImageDecoder.h9
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp5
-rw-r--r--libs/hwui/hwui/Typeface.cpp13
-rw-r--r--libs/hwui/hwui/Typeface.h3
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp3
-rw-r--r--[-rwxr-xr-x]libs/hwui/jni/Bitmap.cpp320
-rw-r--r--libs/hwui/jni/Bitmap.h1
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp180
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp163
-rw-r--r--libs/hwui/jni/BufferUtils.cpp130
-rw-r--r--libs/hwui/jni/BufferUtils.h32
-rw-r--r--libs/hwui/jni/ByteBufferStreamAdaptor.cpp1
-rw-r--r--libs/hwui/jni/ColorFilter.cpp1
-rw-r--r--libs/hwui/jni/FontFamily.cpp5
-rw-r--r--libs/hwui/jni/GIFMovie.cpp2
-rw-r--r--libs/hwui/jni/Gainmap.cpp267
-rw-r--r--libs/hwui/jni/Graphics.cpp48
-rw-r--r--libs/hwui/jni/GraphicsJNI.h55
-rw-r--r--libs/hwui/jni/HardwareBufferHelpers.cpp68
-rw-r--r--libs/hwui/jni/HardwareBufferHelpers.h38
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp59
-rw-r--r--libs/hwui/jni/Interpolator.cpp4
-rw-r--r--libs/hwui/jni/JvmErrorReporter.h47
-rw-r--r--libs/hwui/jni/MaskFilter.cpp1
-rw-r--r--libs/hwui/jni/MeshSpecification.cpp164
-rw-r--r--libs/hwui/jni/Movie.cpp2
-rw-r--r--libs/hwui/jni/Movie.h1
-rw-r--r--libs/hwui/jni/MovieImpl.cpp9
-rw-r--r--libs/hwui/jni/NinePatch.cpp2
-rw-r--r--libs/hwui/jni/NinePatchPeeker.cpp2
-rw-r--r--libs/hwui/jni/Paint.cpp256
-rw-r--r--libs/hwui/jni/Path.cpp123
-rw-r--r--libs/hwui/jni/PathEffect.cpp6
-rw-r--r--libs/hwui/jni/PathIterator.cpp81
-rw-r--r--libs/hwui/jni/RenderEffect.cpp1
-rw-r--r--libs/hwui/jni/ScopedParcel.cpp85
-rw-r--r--libs/hwui/jni/ScopedParcel.h63
-rw-r--r--libs/hwui/jni/Shader.cpp87
-rw-r--r--libs/hwui/jni/Typeface.cpp118
-rw-r--r--libs/hwui/jni/Utils.cpp3
-rw-r--r--libs/hwui/jni/Utils.h3
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.cpp191
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.h51
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp93
-rw-r--r--libs/hwui/jni/android_graphics_ColorSpace.cpp50
-rw-r--r--libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp184
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp187
-rw-r--r--libs/hwui/jni/android_graphics_Matrix.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_Mesh.cpp205
-rw-r--r--libs/hwui/jni/fonts/Font.cpp11
-rw-r--r--libs/hwui/jni/fonts/FontFamily.cpp15
-rw-r--r--libs/hwui/jni/text/GraphemeBreak.cpp67
-rw-r--r--libs/hwui/pipeline/skia/DumpOpsCanvas.h2
-rw-r--r--libs/hwui/pipeline/skia/GLFunctorDrawable.cpp3
-rw-r--r--libs/hwui/pipeline/skia/HolePunch.h1
-rw-r--r--libs/hwui/pipeline/skia/LayerDrawable.cpp61
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp7
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.h1
-rw-r--r--libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp7
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp4
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h5
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp8
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp69
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h16
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp56
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h18
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp8
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.h9
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp102
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.h10
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp59
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h25
-rw-r--r--libs/hwui/pipeline/skia/StretchMask.cpp8
-rw-r--r--libs/hwui/pipeline/skia/TransformCanvas.cpp10
-rw-r--r--libs/hwui/pipeline/skia/TransformCanvas.h7
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp2
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp193
-rw-r--r--libs/hwui/renderthread/CacheManager.h46
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp165
-rw-r--r--libs/hwui/renderthread/CanvasContext.h71
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp146
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h38
-rw-r--r--libs/hwui/renderthread/EglManager.cpp14
-rw-r--r--libs/hwui/renderthread/EglManager.h1
-rw-r--r--libs/hwui/renderthread/HardwareBufferRenderParams.h62
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp164
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h57
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h20
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp71
-rw-r--r--libs/hwui/renderthread/RenderProxy.h21
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp16
-rw-r--r--libs/hwui/renderthread/RenderThread.h6
-rw-r--r--libs/hwui/renderthread/VulkanManager.h3
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp36
-rw-r--r--libs/hwui/renderthread/VulkanSurface.h4
-rw-r--r--libs/hwui/tests/common/BitmapAllocationTestUtils.h5
-rw-r--r--libs/hwui/tests/common/CallCountingCanvas.h2
-rw-r--r--libs/hwui/tests/common/TestContext.cpp12
-rw-r--r--libs/hwui/tests/common/TestListViewSceneBase.cpp2
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp6
-rw-r--r--libs/hwui/tests/common/TestUtils.h10
-rw-r--r--libs/hwui/tests/common/scenes/BitmapFillrate.cpp1
-rw-r--r--libs/hwui/tests/common/scenes/BitmapShaders.cpp12
-rw-r--r--libs/hwui/tests/common/scenes/ClippingAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/HwBitmap565.cpp6
-rw-r--r--libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/HwLayerAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/JankyScene.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp1
-rw-r--r--libs/hwui/tests/common/scenes/ListViewAnimation.cpp11
-rw-r--r--libs/hwui/tests/common/scenes/MagnifierAnimation.cpp55
-rw-r--r--libs/hwui/tests/common/scenes/OvalAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/PathClippingAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp6
-rw-r--r--libs/hwui/tests/common/scenes/RecentsAnimation.cpp5
-rw-r--r--libs/hwui/tests/common/scenes/RectGridAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp1
-rw-r--r--libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/ShapeAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp4
-rw-r--r--libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp1
-rw-r--r--libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp13
-rw-r--r--libs/hwui/tests/common/scenes/TextAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/TvApp.cpp5
-rw-r--r--libs/hwui/tests/microbench/DisplayListCanvasBench.cpp2
-rw-r--r--libs/hwui/tests/microbench/RenderNodeBench.cpp2
-rw-r--r--libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp2
-rw-r--r--libs/hwui/tests/unit/CacheManagerTests.cpp10
-rw-r--r--libs/hwui/tests/unit/CanvasContextTests.cpp4
-rw-r--r--libs/hwui/tests/unit/CanvasOpTests.cpp11
-rw-r--r--libs/hwui/tests/unit/EglManagerTests.cpp2
-rw-r--r--libs/hwui/tests/unit/FatalTestCanvas.h2
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp11
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp4
-rw-r--r--libs/hwui/tests/unit/SkiaBehaviorTests.cpp7
-rw-r--r--libs/hwui/tests/unit/SkiaCanvasTests.cpp10
-rw-r--r--libs/hwui/tests/unit/SkiaDisplayListTests.cpp4
-rw-r--r--libs/hwui/tests/unit/SkiaPipelineTests.cpp1
-rw-r--r--libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp1
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp72
-rw-r--r--libs/hwui/tests/unit/VectorDrawableTests.cpp6
-rw-r--r--libs/hwui/utils/AutoMalloc.h94
-rw-r--r--libs/hwui/utils/Color.cpp60
-rw-r--r--libs/hwui/utils/Color.h2
-rw-r--r--libs/hwui/utils/PaintUtils.h1
-rw-r--r--libs/input/MouseCursorController.cpp145
-rw-r--r--libs/input/MouseCursorController.h21
-rw-r--r--libs/input/PointerController.cpp103
-rw-r--r--libs/input/PointerController.h35
-rw-r--r--libs/input/PointerControllerContext.h12
-rw-r--r--libs/input/SpriteController.cpp37
-rw-r--r--libs/input/SpriteController.h2
-rw-r--r--libs/input/SpriteIcon.h10
-rw-r--r--libs/input/TouchSpotController.cpp35
-rw-r--r--libs/input/TouchSpotController.h3
-rw-r--r--libs/input/tests/PointerController_test.cpp107
-rw-r--r--libs/securebox/Android.bp8
-rw-r--r--libs/securebox/OWNERS1
-rw-r--r--libs/securebox/src/com/android/security/SecureBox.java461
-rw-r--r--libs/securebox/tests/Android.bp46
-rw-r--r--libs/securebox/tests/AndroidManifest.xml33
-rw-r--r--libs/securebox/tests/AndroidTest.xml31
-rw-r--r--libs/securebox/tests/src/com/android/security/SecureBoxTest.java371
-rw-r--r--libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java4
756 files changed, 34710 insertions, 14261 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 68ff806c6765..65955b1d9bcc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -21,6 +21,7 @@ import static androidx.window.util.ExtensionHelper.isZero;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -33,7 +34,8 @@ import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** A representation of a folding feature for both Extension and Sidecar.
+/**
+ * A representation of a folding feature for both Extension and Sidecar.
* For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
* {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
* {@link androidx.window.extensions.layout.FoldingFeature}.
@@ -67,10 +69,11 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_UNKNOWN = -1;
/**
- * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
- * and Extensions do not match exactly.
+ * A common state that contains no folding features. For example, an in-folding device in the
+ * "closed" device state.
*/
- public static final int COMMON_STATE_FLAT = 3;
+ public static final int COMMON_STATE_NO_FOLDING_FEATURES = 1;
+
/**
* A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
* Sidecar and Extensions do not match exactly.
@@ -78,9 +81,27 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_HALF_OPENED = 2;
/**
- * The possible states for a folding hinge.
+ * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
+ * and Extensions do not match exactly.
+ */
+ public static final int COMMON_STATE_FLAT = 3;
+
+ /**
+ * A common state where the hinge state should be derived using the base state from
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)} instead of the
+ * emulated state. This is an internal state and must not be passed to clients.
*/
- @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
+ public static final int COMMON_STATE_USE_BASE_STATE = 1000;
+
+ /**
+ * The possible states for a folding hinge. Common in this context means normalized between
+ * extensions and sidecar.
+ */
+ @IntDef({COMMON_STATE_UNKNOWN,
+ COMMON_STATE_NO_FOLDING_FEATURES,
+ COMMON_STATE_HALF_OPENED,
+ COMMON_STATE_FLAT,
+ COMMON_STATE_USE_BASE_STATE})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
@@ -167,7 +188,7 @@ public final class CommonFoldingFeature {
}
String stateString = featureMatcher.group(6);
stateString = stateString == null ? "" : stateString;
- final int state;
+ @State final int state;
switch (stateString) {
case PATTERN_STATE_FLAT:
state = COMMON_STATE_FLAT;
@@ -191,8 +212,8 @@ public final class CommonFoldingFeature {
@NonNull
private final Rect mRect;
- CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
- assertValidState(state);
+ CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
+ assertReportableState(state);
this.mType = type;
this.mState = state;
if (rect.width() == 0 && rect.height() == 0) {
@@ -231,13 +252,22 @@ public final class CommonFoldingFeature {
}
@Override
+ public String toString() {
+ return "CommonFoldingFeature=[Type: " + mType + ", state: " + mState + "]";
+ }
+
+ @Override
public int hashCode() {
return Objects.hash(mType, mState, mRect);
}
- private static void assertValidState(@Nullable Integer state) {
- if (state != null && state != COMMON_STATE_FLAT
- && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
+ /**
+ * Checks if the provided folding feature state should be reported to clients. See
+ * {@link androidx.window.extensions.layout.FoldingFeature}
+ */
+ private static void assertReportableState(@State int state) {
+ if (state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED
+ && state != COMMON_STATE_UNKNOWN) {
throw new IllegalArgumentException("Invalid state: " + state
+ "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 0bdf98c8680f..66f27f517ab3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,6 +19,7 @@ package androidx.window.common;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
@@ -52,13 +53,54 @@ public final class DeviceStateManagerFoldingFeatureProducer
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+ * {@link CommonFoldingFeature.State} map.
+ */
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+ /**
+ * Emulated device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+ * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
+ * physical device states. They represent the state of the device when various software
+ * features and APIs are applied. The emulated states generally consist of all "base" states,
+ * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
+ * example is activated via public API and can be active in both the "open" and "half folded"
+ * device states.
+ */
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ /**
+ * Base device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
+ * "Base" in this context means the "physical" state of the device.
+ */
+ private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+
@NonNull
private final BaseDataProducer<String> mRawFoldSupplier;
+ private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+ @Override
+ public void onStateChanged(int state) {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ mCurrentBaseDeviceState = state;
+
+ if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
+ == COMMON_STATE_USE_BASE_STATE) {
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+ }
+ };
+
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
@NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
@@ -92,12 +134,8 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
if (mDeviceStateToPostureMap.size() > 0) {
- DeviceStateCallback deviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
- };
Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
- .registerCallback(context.getMainExecutor(), deviceStateCallback);
+ .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
}
}
@@ -178,11 +216,18 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
- final int globalHingeState = globalHingeState();
- return parseListFromString(displayFeaturesString, globalHingeState);
+ return parseListFromString(displayFeaturesString, currentHingeState());
}
- private int globalHingeState() {
- return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+ @CommonFoldingFeature.State
+ private int currentHingeState() {
+ @CommonFoldingFeature.State
+ int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+
+ if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
+ posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+ }
+
+ return posture;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 666b472c3716..76e0e1eb7a95 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 2;
+ return 3;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
new file mode 100644
index 000000000000..1ff169433b9d
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.view.Display;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
+
+/**
+ * {@link Presentation} object that is used to present extra content
+ * on the rear facing display when in a rear display presentation feature.
+ */
+class RearDisplayPresentation extends Presentation implements ExtensionWindowAreaPresentation {
+
+ @NonNull
+ private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer;
+
+ RearDisplayPresentation(@NonNull Context outerContext, @NonNull Display display,
+ @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) {
+ super(outerContext, display);
+ mStateConsumer = stateConsumer;
+ }
+
+ /**
+ * {@code mStateConsumer} is notified that their content is now visible when the
+ * {@link Presentation} object is started. There is no comparable callback for
+ * {@link WindowAreaComponent#SESSION_STATE_INVISIBLE} in {@link #onStop()} due to the
+ * timing of when a {@link android.hardware.devicestate.DeviceStateRequest} is cancelled
+ * ending rear display presentation mode happening before the {@link Presentation} is stopped.
+ */
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_VISIBLE);
+ }
+
+ @NonNull
+ @Override
+ public Context getPresentationContext() {
+ return getContext();
+ }
+
+ @Override
+ public void setPresentationView(View view) {
+ setContentView(view);
+ if (!isShowing()) {
+ show();
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java
new file mode 100644
index 000000000000..141a6ad48771
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_ACTIVE;
+import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_INACTIVE;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Consumer;
+
+import java.util.Objects;
+
+/**
+ * Controller class that keeps track of the status of the device state request
+ * to enable the rear display presentation feature. This controller notifies the session callback
+ * when the state request is active, and notifies the callback when the request is canceled.
+ *
+ * Clients are notified via {@link Consumer} provided with
+ * {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} values to signify
+ * when the request becomes active and cancelled.
+ */
+class RearDisplayPresentationController implements DeviceStateRequest.Callback {
+
+ private static final String TAG = "RearDisplayPresentationController";
+
+ // Original context that requested to enable rear display presentation mode
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer;
+ @Nullable
+ private ExtensionWindowAreaPresentation mExtensionWindowAreaPresentation;
+ @NonNull
+ private final DisplayManager mDisplayManager;
+
+ /**
+ * Creates the RearDisplayPresentationController
+ * @param context Originating {@link android.content.Context} that is initiating the rear
+ * display presentation session.
+ * @param stateConsumer {@link Consumer} that will be notified that the session is active when
+ * the device state request is active and the session has been created. If the device
+ * state request is cancelled, the callback will be notified that the session has been
+ * ended. This could occur through a call to cancel the feature or if the device is
+ * manipulated in a way that cancels any device state override.
+ */
+ RearDisplayPresentationController(@NonNull Context context,
+ @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(stateConsumer);
+
+ mContext = context;
+ mStateConsumer = stateConsumer;
+ mDisplayManager = context.getSystemService(DisplayManager.class);
+ }
+
+ @Override
+ public void onRequestActivated(@NonNull DeviceStateRequest request) {
+ Display[] rearDisplays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR);
+ if (rearDisplays.length == 0) {
+ mStateConsumer.accept(SESSION_STATE_INACTIVE);
+ Log.e(TAG, "Rear display list should not be empty");
+ return;
+ }
+
+ mExtensionWindowAreaPresentation =
+ new RearDisplayPresentation(mContext, rearDisplays[0], mStateConsumer);
+ mStateConsumer.accept(SESSION_STATE_ACTIVE);
+ }
+
+ @Override
+ public void onRequestCanceled(@NonNull DeviceStateRequest request) {
+ mStateConsumer.accept(SESSION_STATE_INACTIVE);
+ }
+
+ @Nullable
+ public ExtensionWindowAreaPresentation getWindowAreaPresentation() {
+ return mExtensionWindowAreaPresentation;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java
new file mode 100644
index 000000000000..0b1423ae48c0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Class that provides information around the current status of a window area feature. Contains
+ * the current {@link WindowAreaComponent.WindowAreaStatus} value corresponding to the
+ * rear display presentation feature, as well as the {@link DisplayMetrics} for the rear facing
+ * display.
+ */
+class RearDisplayPresentationStatus implements ExtensionWindowAreaStatus {
+
+ @WindowAreaComponent.WindowAreaStatus
+ private final int mWindowAreaStatus;
+
+ @NonNull
+ private final DisplayMetrics mDisplayMetrics;
+
+ RearDisplayPresentationStatus(@WindowAreaComponent.WindowAreaStatus int status,
+ @NonNull DisplayMetrics displayMetrics) {
+ mWindowAreaStatus = status;
+ mDisplayMetrics = displayMetrics;
+ }
+
+ /**
+ * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus}
+ * value that relates to the current status of a feature.
+ */
+ @Override
+ @WindowAreaComponent.WindowAreaStatus
+ public int getWindowAreaStatus() {
+ return mWindowAreaStatus;
+ }
+
+ /**
+ * Returns the {@link DisplayMetrics} that corresponds to the window area that a feature
+ * interacts with. This is converted to size class information provided to developers.
+ */
+ @Override
+ @NonNull
+ public DisplayMetrics getWindowAreaDisplayMetrics() {
+ return mDisplayMetrics;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 9118ee2bf125..575b0cea78f7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -22,13 +22,20 @@ import android.app.Activity;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
+import android.hardware.display.DisplayManager;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayAddress;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.concurrent.Executor;
@@ -46,61 +53,102 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
private final Object mLock = new Object();
+ @NonNull
private final DeviceStateManager mDeviceStateManager;
+ @NonNull
+ private final DisplayManager mDisplayManager;
+ @NonNull
private final Executor mExecutor;
@GuardedBy("mLock")
private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final ArraySet<Consumer<ExtensionWindowAreaStatus>>
+ mRearDisplayPresentationStatusListeners = new ArraySet<>();
private final int mRearDisplayState;
+ private final int mConcurrentDisplayState;
+ @NonNull
+ private final int[] mFoldedDeviceStates;
+ @NonNull
+ private long mRearDisplayAddress = 0;
@WindowAreaSessionState
private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
@GuardedBy("mLock")
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
@GuardedBy("mLock")
- private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
+ private int[] mCurrentSupportedDeviceStates;
+
+ @GuardedBy("mLock")
+ private DeviceStateRequest mRearDisplayStateRequest;
+ @GuardedBy("mLock")
+ private RearDisplayPresentationController mRearDisplayPresentationController;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private DisplayMetrics mRearDisplayMetrics;
+
+ @WindowAreaSessionState
@GuardedBy("mLock")
- private DeviceStateRequest mDeviceStateRequest;
+ private int mLastReportedRearDisplayPresentationStatus;
public WindowAreaComponentImpl(@NonNull Context context) {
mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mDisplayManager = context.getSystemService(DisplayManager.class);
mExecutor = context.getMainExecutor();
+ mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ R.array.config_foldedDeviceStates);
+
// TODO(b/236022708) Move rear display state to device state config file
mRearDisplayState = context.getResources().getInteger(
R.integer.config_deviceStateRearDisplay);
+ mConcurrentDisplayState = context.getResources().getInteger(
+ R.integer.config_deviceStateConcurrentRearDisplay);
+
mDeviceStateManager.registerCallback(mExecutor, this);
+ if (mConcurrentDisplayState != INVALID_DEVICE_STATE) {
+ mRearDisplayAddress = Long.parseLong(context.getResources().getString(
+ R.string.config_rearDisplayPhysicalAddress));
+ }
}
/**
* Adds a listener interested in receiving updates on the RearDisplayStatus
* of the device. Because this is being called from the OEM provided
- * extensions, we will post the result of the listener on the executor
+ * extensions, the result of the listener will be posted on the executor
* provided by the developer at the initial call site.
*
- * Depending on the initial state of the device, we will return either
+ * Rear display mode moves the calling application to the display on the device that is
+ * facing the same direction as the rear cameras. This would be the cover display on a fold-in
+ * style device when the device is opened.
+ *
+ * Depending on the initial state of the device, the {@link Consumer} will receive either
* {@link WindowAreaComponent#STATUS_AVAILABLE} or
* {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
- * state respectively. When the rear display feature is triggered, we update the status to be
- * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
+ * state respectively. When the rear display feature is triggered, the status is updated to be
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+ * TODO(b/240727590): Prefix with AREA_
*
- * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
- * enabled.
+ * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
+ * enabled.
*
* @param consumer {@link Consumer} interested in receiving updates to the status of
* rear display mode.
*/
+ @Override
public void addRearDisplayStatusListener(
@NonNull Consumer<@WindowAreaStatus Integer> consumer) {
synchronized (mLock) {
mRearDisplayStatusListeners.add(consumer);
- // If current device state is still invalid, we haven't gotten our initial value yet
+ // If current device state is still invalid, the initial value has not been provided.
if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
return;
}
- consumer.accept(getCurrentStatus());
+ consumer.accept(getCurrentRearDisplayModeStatus());
}
}
@@ -108,6 +156,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
* Removes a listener no longer interested in receiving updates.
* @param consumer no longer interested in receiving updates to RearDisplayStatus
*/
+ @Override
public void removeRearDisplayStatusListener(
@NonNull Consumer<@WindowAreaStatus Integer> consumer) {
synchronized (mLock) {
@@ -118,13 +167,17 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
/**
* Creates and starts a rear display session and provides updates to the
* callback provided. Because this is being called from the OEM provided
- * extensions, we will post the result of the listener on the executor
+ * extensions, the result of the listener will be posted on the executor
* provided by the developer at the initial call site.
*
- * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
+ * Rear display mode moves the calling application to the display on the device that is
+ * facing the same direction as the rear cameras. This would be the cover display on a fold-in
+ * style device when the device is opened.
+ *
+ * When rear display mode is enabled, a request is made to {@link DeviceStateManager}
* to override the device state to the state that corresponds to RearDisplay
- * mode. When the {@link DeviceStateRequest} is activated, we let the
- * consumer know that the session is active by sending
+ * mode. When the {@link DeviceStateRequest} is activated, the provided {@link Consumer} is
+ * notified that the session is active by receiving
* {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
*
* @param activity to provide updates to the client on
@@ -132,19 +185,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
* @param rearDisplaySessionCallback to provide updates to the client on
* the status of the Session
*/
+ @Override
public void startRearDisplaySession(@NonNull Activity activity,
@NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
synchronized (mLock) {
- if (mDeviceStateRequest != null) {
+ if (mRearDisplayStateRequest != null) {
// Rear display session is already active
throw new IllegalStateException(
"Unable to start new rear display session as one is already active");
}
- mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
+ mRearDisplayStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
mDeviceStateManager.requestState(
- mDeviceStateRequest,
+ mRearDisplayStateRequest,
mExecutor,
- new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
+ new RearDisplayStateRequestCallbackAdapter(rearDisplaySessionCallback)
);
}
}
@@ -152,13 +206,14 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
/**
* Ends the current rear display session and provides updates to the
* callback provided. Because this is being called from the OEM provided
- * extensions, we will post the result of the listener on the executor
- * provided by the developer.
+ * extensions, the result of the listener will be posted on the executor
+ * provided by the developer at the initial call site.
*/
+ @Override
public void endRearDisplaySession() {
synchronized (mLock) {
- if (mDeviceStateRequest != null || isRearDisplayActive()) {
- mDeviceStateRequest = null;
+ if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
+ mRearDisplayStateRequest = null;
mDeviceStateManager.cancelStateRequest();
} else {
throw new IllegalStateException(
@@ -167,13 +222,178 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
}
}
+ /**
+ * Adds a listener interested in receiving updates on the RearDisplayPresentationStatus
+ * of the device. Because this is being called from the OEM provided
+ * extensions, the result of the listener will be posted on the executor
+ * provided by the developer at the initial call site.
+ *
+ * Rear display presentation mode is a feature where an {@link Activity} can present
+ * additional content on a device with a second display that is facing the same direction
+ * as the rear camera (i.e. the cover display on a fold-in style device). The calling
+ * {@link Activity} does not move, whereas in rear display mode it does.
+ *
+ * This listener receives a {@link Pair} with the first item being the
+ * {@link WindowAreaComponent.WindowAreaStatus} that corresponds to the current status of the
+ * feature, and the second being the {@link DisplayMetrics} of the display that would be
+ * presented to when the feature is active.
+ *
+ * Depending on the initial state of the device, the {@link Consumer} will receive either
+ * {@link WindowAreaComponent#STATUS_AVAILABLE} or
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE} for the status value of the {@link Pair} if
+ * the feature is supported or not in that state respectively. Rear display presentation mode is
+ * currently not supported when the device is folded. When the rear display presentation feature
+ * is triggered, the status is updated to be {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+ * TODO(b/240727590): Prefix with AREA_
+ *
+ * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
+ * enabled.
+ *
+ * @param consumer {@link Consumer} interested in receiving updates to the status of
+ * rear display presentation mode.
+ */
+ @Override
+ public void addRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+ synchronized (mLock) {
+ mRearDisplayPresentationStatusListeners.add(consumer);
+
+ // If current device state is still invalid, the initial value has not been provided
+ if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+ return;
+ }
+ @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
+ DisplayMetrics metrics =
+ currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
+ consumer.accept(
+ new RearDisplayPresentationStatus(currentStatus, metrics));
+ }
+ }
+
+ /**
+ * Removes a listener no longer interested in receiving updates.
+ * @param consumer no longer interested in receiving updates to RearDisplayPresentationStatus
+ */
+ @Override
+ public void removeRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+ synchronized (mLock) {
+ mRearDisplayPresentationStatusListeners.remove(consumer);
+ }
+ }
+
+ /**
+ * Creates and starts a rear display presentation session and sends state updates to the
+ * consumer provided. This consumer will receive a constant represented by
+ * {@link WindowAreaSessionState} to represent the state of the current rear display
+ * session. It will be translated to a more friendly interface in the library.
+ *
+ * Because this is being called from the OEM provided extensions, the library
+ * will post the result of the listener on the executor provided by the developer.
+ *
+ * Rear display presentation mode refers to a feature where an {@link Activity} can present
+ * additional content on a device with a second display that is facing the same direction
+ * as the rear camera (i.e. the cover display on a fold-in style device). The calling
+ * {@link Activity} stays on the user-facing display.
+ *
+ * @param activity that the OEM implementation will use as a base
+ * context and to identify the source display area of the request.
+ * The reference to the activity instance must not be stored in the OEM
+ * implementation to prevent memory leaks.
+ * @param consumer to provide updates to the client on the status of the session
+ * @throws UnsupportedOperationException if this method is called when rear display presentation
+ * mode is not available. This could be to an incompatible device state or when
+ * another process is currently in this mode.
+ */
+ @Override
+ public void startRearDisplayPresentationSession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
+ synchronized (mLock) {
+ if (mRearDisplayPresentationController != null) {
+ // Rear display presentation session is already active
+ throw new IllegalStateException(
+ "Unable to start new rear display presentation session as one is already "
+ + "active");
+ }
+ if (getCurrentRearDisplayPresentationModeStatus()
+ != WindowAreaComponent.STATUS_AVAILABLE) {
+ throw new IllegalStateException(
+ "Unable to start new rear display presentation session as the feature is "
+ + "is not currently available");
+ }
+
+ mRearDisplayPresentationController = new RearDisplayPresentationController(activity,
+ stateStatus -> {
+ synchronized (mLock) {
+ if (stateStatus == SESSION_STATE_INACTIVE) {
+ // If the last reported session status was VISIBLE
+ // then the INVISIBLE state should be dispatched before INACTIVE
+ // due to not having a good mechanism to know when
+ // the content is no longer visible before it's fully removed
+ if (getLastReportedRearDisplayPresentationStatus()
+ == SESSION_STATE_VISIBLE) {
+ consumer.accept(SESSION_STATE_INVISIBLE);
+ }
+ mRearDisplayPresentationController = null;
+ }
+ mLastReportedRearDisplayPresentationStatus = stateStatus;
+ consumer.accept(stateStatus);
+ }
+ });
+
+ DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder(
+ mConcurrentDisplayState).build();
+ mDeviceStateManager.requestState(
+ concurrentDisplayStateRequest,
+ mExecutor,
+ mRearDisplayPresentationController
+ );
+ }
+ }
+
+ /**
+ * Ends the current rear display presentation session and provides updates to the
+ * callback provided. When this is ended, the presented content from the calling
+ * {@link Activity} will also be removed from the rear facing display.
+ * Because this is being called from the OEM provided extensions, the result of the listener
+ * will be posted on the executor provided by the developer at the initial call site.
+ *
+ * Cancelling the {@link DeviceStateRequest} and exiting the rear display presentation state,
+ * will remove the presentation window from the cover display as the cover display is no longer
+ * enabled.
+ */
+ @Override
+ public void endRearDisplayPresentationSession() {
+ synchronized (mLock) {
+ if (mRearDisplayPresentationController != null) {
+ mDeviceStateManager.cancelStateRequest();
+ } else {
+ throw new IllegalStateException(
+ "Unable to cancel a rear display presentation session as there is no "
+ + "active session");
+ }
+ }
+ }
+
+ @Nullable
@Override
- public void onBaseStateChanged(int state) {
+ public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
synchronized (mLock) {
- mCurrentDeviceBaseState = state;
- if (state == mCurrentDeviceState) {
- updateStatusConsumers(getCurrentStatus());
+ ExtensionWindowAreaPresentation presentation = null;
+ if (mRearDisplayPresentationController != null) {
+ presentation = mRearDisplayPresentationController.getWindowAreaPresentation();
}
+ return presentation;
+ }
+ }
+
+ @Override
+ public void onSupportedStatesChanged(int[] supportedStates) {
+ synchronized (mLock) {
+ mCurrentSupportedDeviceStates = supportedStates;
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+ updateRearDisplayPresentationStatusListeners(
+ getCurrentRearDisplayPresentationModeStatus());
}
}
@@ -181,17 +401,21 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
public void onStateChanged(int state) {
synchronized (mLock) {
mCurrentDeviceState = state;
- updateStatusConsumers(getCurrentStatus());
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+ updateRearDisplayPresentationStatusListeners(
+ getCurrentRearDisplayPresentationModeStatus());
}
}
+
@GuardedBy("mLock")
- private int getCurrentStatus() {
+ private int getCurrentRearDisplayModeStatus() {
if (mRearDisplayState == INVALID_DEVICE_STATE) {
return WindowAreaComponent.STATUS_UNSUPPORTED;
}
if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+ || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
|| isRearDisplayActive()) {
return WindowAreaComponent.STATUS_UNAVAILABLE;
}
@@ -200,19 +424,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
/**
* Helper method to determine if a rear display session is currently active by checking
- * if the current device configuration matches that of rear display. This would be true
- * if there is a device override currently active (base state != current state) and the current
- * state is that which corresponds to {@code mRearDisplayState}
- * @return {@code true} if the device is in rear display mode and {@code false} if not
+ * if the current device state is that which corresponds to {@code mRearDisplayState}.
+ *
+ * @return {@code true} if the device is in rear display state {@code false} if not
*/
@GuardedBy("mLock")
private boolean isRearDisplayActive() {
- return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
- == mRearDisplayState);
+ return mCurrentDeviceState == mRearDisplayState;
}
@GuardedBy("mLock")
- private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
+ private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) {
+ if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ return;
+ }
synchronized (mLock) {
for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
@@ -220,26 +445,99 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
}
}
+ @GuardedBy("mLock")
+ private int getCurrentRearDisplayPresentationModeStatus() {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
+ if (mCurrentDeviceState == mConcurrentDisplayState
+ || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
+ || isDeviceFolded()) {
+ return WindowAreaComponent.STATUS_UNAVAILABLE;
+ }
+ return WindowAreaComponent.STATUS_AVAILABLE;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isDeviceFolded() {
+ return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState);
+ }
+
+ @GuardedBy("mLock")
+ private void updateRearDisplayPresentationStatusListeners(
+ @WindowAreaStatus int windowAreaStatus) {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ return;
+ }
+ RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus(
+ windowAreaStatus, getRearDisplayMetrics());
+ synchronized (mLock) {
+ for (int i = 0; i < mRearDisplayPresentationStatusListeners.size(); i++) {
+ mRearDisplayPresentationStatusListeners.valueAt(i).accept(consumerValue);
+ }
+ }
+ }
+
+ /**
+ * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
+ * display was not found in the display list, but we have already computed the
+ * {@link DisplayMetrics} for that display, we return the cached value.
+ *
+ * TODO(b/267563768): Update with guidance from Display team for missing displays.
+ *
+ * @throws IllegalArgumentException if the display is not found and there is no cached
+ * {@link DisplayMetrics} for this display.
+ */
+ @GuardedBy("mLock")
+ private DisplayMetrics getRearDisplayMetrics() {
+ Display[] displays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+ for (int i = 0; i < displays.length; i++) {
+ DisplayAddress.Physical address =
+ (DisplayAddress.Physical) displays[i].getAddress();
+ if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ if (mRearDisplayMetrics == null) {
+ mRearDisplayMetrics = new DisplayMetrics();
+ }
+ displays[i].getRealMetrics(mRearDisplayMetrics);
+ return mRearDisplayMetrics;
+ }
+ }
+ if (mRearDisplayMetrics != null) {
+ return mRearDisplayMetrics;
+ } else {
+ throw new IllegalArgumentException(
+ "No display found with the provided display address");
+ }
+ }
+
+ @GuardedBy("mLock")
+ @WindowAreaSessionState
+ private int getLastReportedRearDisplayPresentationStatus() {
+ return mLastReportedRearDisplayPresentationStatus;
+ }
+
/**
* Callback for the {@link DeviceStateRequest} to be notified of when the request has been
* activated or cancelled. This callback provides information to the client library
* on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
*/
- private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
+ private class RearDisplayStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
private final Consumer<Integer> mRearDisplaySessionCallback;
- DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
+ RearDisplayStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
mRearDisplaySessionCallback = callback;
}
@Override
public void onRequestActivated(@NonNull DeviceStateRequest request) {
synchronized (mLock) {
- if (request.equals(mDeviceStateRequest)) {
+ if (request.equals(mRearDisplayStateRequest)) {
mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
- updateStatusConsumers(getCurrentStatus());
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
}
}
}
@@ -247,46 +545,13 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@Override
public void onRequestCanceled(DeviceStateRequest request) {
synchronized (mLock) {
- if (request.equals(mDeviceStateRequest)) {
- mDeviceStateRequest = null;
+ if (request.equals(mRearDisplayStateRequest)) {
+ mRearDisplayStateRequest = null;
}
mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
- updateStatusConsumers(getCurrentStatus());
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
}
}
}
-
- @Override
- public void addRearDisplayPresentationStatusListener(
- @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
- throw new UnsupportedOperationException(
- "addRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
- }
-
- @Override
- public void removeRearDisplayPresentationStatusListener(
- @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
- throw new UnsupportedOperationException(
- "removeRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
- }
-
- @Override
- public void startRearDisplayPresentationSession(@NonNull Activity activity,
- @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
- throw new UnsupportedOperationException(
- "startRearDisplayPresentationSession is not supported in API_VERSION=2");
- }
-
- @Override
- public void endRearDisplayPresentationSession() {
- throw new UnsupportedOperationException(
- "endRearDisplayPresentationSession is not supported in API_VERSION=2");
- }
-
- @Override
- public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
- throw new UnsupportedOperationException(
- "getRearDisplayPresentation is not supported in API_VERSION=2");
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 00e13c94ea90..d94e8e426c4b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -109,38 +109,38 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* be resized based on {@param launchingFragmentBounds}.
* Otherwise, we will create a new TaskFragment with the given
* token for the {@param launchingActivity}.
- * @param launchingFragmentBounds the initial bounds for the launching TaskFragment.
+ * @param launchingRelBounds the initial relative bounds for the launching TaskFragment.
* @param launchingActivity the Activity to put on the left hand side of the split as the
* primary.
* @param secondaryFragmentToken token to create the secondary TaskFragment with.
- * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment
+ * @param secondaryRelBounds the initial relative bounds for the secondary TaskFragment
* @param activityIntent Intent to start the secondary Activity with.
* @param activityOptions ActivityOptions to start the secondary Activity with.
* @param windowingMode the windowing mode to set for the TaskFragments.
* @param splitAttributes the {@link SplitAttributes} to represent the split.
*/
void startActivityToSide(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
+ @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingRelBounds,
@NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
- @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
+ @NonNull Rect secondaryRelBounds, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
@WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
if (mFragmentInfos.containsKey(launchingFragmentToken)) {
- resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
+ resizeTaskFragment(wct, launchingFragmentToken, launchingRelBounds);
updateWindowingMode(wct, launchingFragmentToken, windowingMode);
} else {
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
- launchingFragmentBounds, windowingMode, launchingActivity);
+ launchingRelBounds, windowingMode, launchingActivity);
}
updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
// Create a TaskFragment for the secondary activity.
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), secondaryFragmentToken, ownerToken)
- .setInitialBounds(secondaryFragmentBounds)
+ .setInitialRelativeBounds(secondaryRelBounds)
.setWindowingMode(windowingMode)
// Make sure to set the paired fragment token so that the new TaskFragment will be
// positioned right above the paired TaskFragment.
@@ -154,7 +154,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setAdjacentTaskFragmentsWithRule(wct, launchingFragmentToken, secondaryFragmentToken, rule);
setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
false /* isStacked */);
}
@@ -167,7 +167,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
- setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, fragmentToken);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
@@ -190,8 +190,9 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* have to be a child of this task fragment, but must belong to the same task.
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ @NonNull IBinder ownerToken, @NonNull Rect relBounds,
+ @WindowingMode int windowingMode) {
+ createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode,
null /* pairedActivityToken */);
}
@@ -203,11 +204,11 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* positioned right above it.
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
+ @NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode,
@Nullable IBinder pairedActivityToken) {
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), fragmentToken, ownerToken)
- .setInitialBounds(bounds)
+ .setInitialRelativeBounds(relBounds)
.setWindowingMode(windowingMode)
.setPairedActivityToken(pairedActivityToken)
.build();
@@ -229,29 +230,45 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* have to be a child of this task fragment, but must belong to the same task.
*/
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds,
@WindowingMode int windowingMode, @NonNull Activity activity) {
final IBinder reparentActivityToken = activity.getActivityToken();
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+ createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode,
reparentActivityToken);
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
- void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
+ /**
+ * Sets the two given TaskFragments as adjacent to each other with respecting the given
+ * {@link SplitRule} for {@link WindowContainerTransaction.TaskFragmentAdjacentParams}.
+ */
+ void setAdjacentTaskFragmentsWithRule(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
final boolean finishSecondaryWithPrimary =
- splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
+ SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
final boolean finishPrimaryWithSecondary =
- splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
+ SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
}
+ setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ // Clear primary will also clear secondary.
+ wct.clearAdjacentTaskFragments(fragmentToken);
+ }
+
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
boolean isStacked) {
@@ -262,7 +279,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
} else {
finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
}
- wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+ setCompanionTaskFragment(wct, primary, finishPrimaryWithSecondary ? secondary : null);
final boolean finishSecondaryWithPrimary;
if (isStacked) {
@@ -271,19 +288,24 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
} else {
finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
}
- wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ setCompanionTaskFragment(wct, secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ wct.setCompanionTaskFragment(primary, secondary);
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @Nullable Rect bounds) {
+ @Nullable Rect relBounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
}
- if (bounds == null) {
- bounds = new Rect();
+ if (relBounds == null) {
+ relBounds = new Rect();
}
- wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
+ wct.setRelativeBounds(mFragmentInfos.get(fragmentToken).getToken(), relBounds);
}
void updateWindowingMode(@NonNull WindowContainerTransaction wct,
@@ -310,16 +332,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
OP_TYPE_SET_ANIMATION_PARAMS)
.setAnimationParams(animationParams)
.build();
- wct.setTaskFragmentOperation(fragmentToken, operation);
+ wct.addTaskFragmentOperation(fragmentToken, operation);
}
void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
- if (!mFragmentInfos.containsKey(fragmentToken)) {
- throw new IllegalArgumentException(
- "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
- }
- wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
+ wct.deleteTaskFragment(fragmentToken);
}
void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 77284c4166bd..18497ad249ee 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,12 +17,15 @@
package androidx.window.extensions.embedding;
import android.app.Activity;
-import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.IBinder;
import android.util.Pair;
import android.util.Size;
+import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Function;
/**
* Client-side descriptor of a split that holds two containers.
@@ -34,8 +37,14 @@ class SplitContainer {
private final TaskFragmentContainer mSecondaryContainer;
@NonNull
private final SplitRule mSplitRule;
+ /** @see SplitContainer#getCurrentSplitAttributes() */
@NonNull
- private SplitAttributes mSplitAttributes;
+ private SplitAttributes mCurrentSplitAttributes;
+ /** @see SplitContainer#getDefaultSplitAttributes() */
+ @NonNull
+ private SplitAttributes mDefaultSplitAttributes;
+ @NonNull
+ private final IBinder mToken;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@@ -45,7 +54,9 @@ class SplitContainer {
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
- mSplitAttributes = splitAttributes;
+ mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
+ mCurrentSplitAttributes = splitAttributes;
+ mToken = new Binder("SplitContainer");
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -78,19 +89,60 @@ class SplitContainer {
return mSplitRule;
}
+ /**
+ * Returns the current {@link SplitAttributes} this {@code SplitContainer} is showing.
+ * <p>
+ * If the {@code SplitAttributes} calculator function is not set by
+ * {@link SplitController#setSplitAttributesCalculator(Function)}, the current
+ * {@code SplitAttributes} is either to expand the containers if the size constraints of
+ * {@link #getSplitRule()} are not satisfied,
+ * or the {@link #getDefaultSplitAttributes()}, otherwise.
+ * </p><p>
+ * If the {@code SplitAttributes} calculator function is set, the current
+ * {@code SplitAttributes} will be customized by the function, which can be any
+ * {@code SplitAttributes}.
+ * </p>
+ *
+ * @see SplitAttributes.SplitType.ExpandContainersSplitType
+ */
+ @NonNull
+ SplitAttributes getCurrentSplitAttributes() {
+ return mCurrentSplitAttributes;
+ }
+
+ /**
+ * Returns the default {@link SplitAttributes} when the parent task container bounds satisfy
+ * {@link #getSplitRule()} constraints.
+ * <p>
+ * The value is usually from {@link SplitRule#getDefaultSplitAttributes} unless it is overridden
+ * by {@link SplitController#updateSplitAttributes(IBinder, SplitAttributes)}.
+ */
+ @NonNull
+ SplitAttributes getDefaultSplitAttributes() {
+ return mDefaultSplitAttributes;
+ }
+
@NonNull
- SplitAttributes getSplitAttributes() {
- return mSplitAttributes;
+ IBinder getToken() {
+ return mToken;
}
/**
* Updates the {@link SplitAttributes} to this container.
* It is usually used when there's a folding state change or
- * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
- * Configuration)}.
+ * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction,
+ * int, TaskFragmentParentInfo)}.
+ */
+ void updateCurrentSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+ mCurrentSplitAttributes = splitAttributes;
+ }
+
+ /**
+ * Overrides the default {@link SplitAttributes} to this container, which may be different
+ * from {@link SplitRule#getDefaultSplitAttributes}.
*/
- void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
- mSplitAttributes = splitAttributes;
+ void updateDefaultSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+ mDefaultSplitAttributes = splitAttributes;
}
@NonNull
@@ -112,7 +164,7 @@ class SplitContainer {
@NonNull
SplitInfo toSplitInfo() {
return new SplitInfo(mPrimaryContainer.toActivityStack(),
- mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
@@ -171,9 +223,10 @@ class SplitContainer {
public String toString() {
return "SplitContainer{"
+ " primaryContainer=" + mPrimaryContainer
- + " secondaryContainer=" + mSecondaryContainer
- + " splitRule=" + mSplitRule
- + " splitAttributes" + mSplitAttributes
+ + ", secondaryContainer=" + mSecondaryContainer
+ + ", splitRule=" + mSplitRule
+ + ", currentSplitAttributes" + mCurrentSplitAttributes
+ + ", defaultSplitAttributes" + mDefaultSplitAttributes
+ "}";
}
}
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 569eb801989b..404429ad41d3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -20,19 +20,19 @@ import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -67,6 +67,7 @@ import android.util.SparseArray;
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -86,6 +87,7 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -98,7 +100,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
ActivityEmbeddingComponent {
static final String TAG = "SplitController";
static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@VisibleForTesting
@GuardedBy("mLock")
@@ -229,6 +231,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return mSplitAttributesCalculator;
}
+ @Override
+ @NonNull
+ public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
+ @NonNull IBinder token) {
+ options.setLaunchTaskFragmentToken(token);
+ return options;
+ }
+
@NonNull
@GuardedBy("mLock")
@VisibleForTesting
@@ -270,6 +280,102 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
+ if (activityStackTokens.isEmpty()) {
+ return;
+ }
+ synchronized (mLock) {
+ // Translate ActivityStack to TaskFragmentContainer.
+ final List<TaskFragmentContainer> pendingFinishingContainers =
+ activityStackTokens.stream()
+ .map(token -> {
+ synchronized (mLock) {
+ return getContainer(token);
+ }
+ }).filter(Objects::nonNull)
+ .toList();
+
+ if (pendingFinishingContainers.isEmpty()) {
+ return;
+ }
+ // Start transaction with close transit type.
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+
+ forAllTaskContainers(taskContainer -> {
+ synchronized (mLock) {
+ final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+ // Clean up the TaskFragmentContainers by the z-order from the lowest.
+ for (int i = 0; i < containers.size(); i++) {
+ final TaskFragmentContainer container = containers.get(i);
+ if (pendingFinishingContainers.contains(container)) {
+ // Don't update records here to prevent double invocation.
+ container.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, this, false /* shouldRemoveRecord */);
+ }
+ }
+ // Remove container records.
+ removeContainers(taskContainer, pendingFinishingContainers);
+ // Update the change to the server side.
+ updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
+ }
+ });
+
+ // Apply the transaction.
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
+ public void invalidateTopVisibleSplitAttributes() {
+ synchronized (mLock) {
+ WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
+ .getTransaction();
+ forAllTaskContainers(taskContainer -> {
+ synchronized (mLock) {
+ updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
+ }
+ });
+ mTransactionManager.getCurrentTransactionRecord()
+ .apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) {
+ for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
+ callback.accept(mTaskContainers.valueAt(i));
+ }
+ }
+
+ @Override
+ public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
+ @NonNull SplitAttributes splitAttributes) {
+ Objects.requireNonNull(splitInfoToken);
+ Objects.requireNonNull(splitAttributes);
+ synchronized (mLock) {
+ final SplitContainer splitContainer = getSplitContainer(splitInfoToken);
+ if (splitContainer == null) {
+ Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken);
+ return;
+ }
+ // Override the default split Attributes so that it will be applied
+ // if the SplitContainer is not visible currently.
+ splitContainer.updateDefaultSplitAttributes(splitAttributes);
+
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) {
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ } else {
+ // Abort if the SplitContainer wasn't updated.
+ transactionRecord.abort();
+ }
+ }
+ }
+
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
@@ -355,7 +461,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
@@ -391,22 +498,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Do not finish the dependents if the last activity is reparented to PiP.
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
cleanupForEnterPip(wct, container);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
// Do not finish the dependents if this TaskFragment was cleared to reorder
// the launching Activity to front of the Task.
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
@@ -417,6 +529,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// All overrides will be cleanup.
container.setLastRequestedBounds(null /* bounds */);
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
+ container.clearLastAdjacentTaskFragment();
+ container.setLastCompanionTaskFragment(null /* fragmentToken */);
+ container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT);
cleanupForEnterPip(wct, container);
} else if (wasInPip) {
// Exit PIP.
@@ -586,11 +701,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
- int opType, @NonNull Throwable exception) {
+ @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
switch (opType) {
- case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
- case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
+ case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
final TaskFragmentContainer container;
if (taskFragmentInfo != null) {
container = getContainer(taskFragmentInfo.getFragmentToken());
@@ -605,7 +720,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
break;
@@ -632,41 +748,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Returns whether the given {@link TaskContainer} may show in split. */
- // Suppress GuardedBy warning because lint asks to mark this method as
- // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
- @SuppressWarnings("GuardedBy")
- @GuardedBy("mLock")
- private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
- // No split inside PIP.
- if (taskContainer.isInPictureInPicture()) {
- return false;
- }
- // Always assume the TaskContainer if SplitAttributesCalculator is set
- if (mSplitAttributesCalculator != null) {
- return true;
- }
- // Check if the parent container bounds can support any split rule.
- for (EmbeddingRule rule : mSplitRules) {
- if (!(rule instanceof SplitRule)) {
- continue;
- }
- final SplitRule splitRule = (SplitRule) rule;
- final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
- taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
- if (shouldShowSplit(splitAttributes)) {
- return true;
- }
- }
- return false;
- }
-
@VisibleForTesting
@GuardedBy("mLock")
void onActivityCreated(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchedActivity) {
- // TODO(b/229680885): we don't support launching into primary yet because we want to always
- // launch the new activity on top.
resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
updateCallbackIfNecessary();
}
@@ -702,6 +787,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
+ final TaskFragmentContainer container = getContainerWithActivity(activity);
+ if (!isOnReparent && container != null
+ && container.getTaskContainer().getTopTaskFragmentContainer() != container) {
+ // Do not resolve if the launched activity is not the top-most container in the Task.
+ return true;
+ }
+
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new launched activity should always expand.
@@ -724,6 +816,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
+ // Skip resolving the following split-rules if the launched activity has been requested
+ // to be launched into its current container.
+ if (container != null && container.isActivityInRequestedTaskFragment(
+ activity.getActivityToken())) {
+ return true;
+ }
+
// 3. Whether the new launched activity has already been in a split with a rule matched.
if (isNewActivityInSplitWithRuleMatched(activity)) {
return true;
@@ -901,7 +1000,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (container != null) {
+ // Looking for the activity below from the information we already have if the container
+ // only embeds activities of the same process because activities of other processes are not
+ // available in this embedding host process for security concern.
+ if (container != null && !container.hasCrossProcessActivities()) {
final List<Activity> containerActivities = container.collectNonFinishingActivities();
final int index = containerActivities.indexOf(activity);
if (index > 0) {
@@ -1022,7 +1124,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
@@ -1343,20 +1446,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Removes the container from bookkeeping records.
*/
void removeContainer(@NonNull TaskFragmentContainer container) {
+ removeContainers(container.getTaskContainer(), Collections.singletonList(container));
+ }
+
+ /**
+ * Removes containers from bookkeeping records.
+ */
+ void removeContainers(@NonNull TaskContainer taskContainer,
+ @NonNull List<TaskFragmentContainer> containers) {
// Remove all split containers that included this one
- final TaskContainer taskContainer = container.getTaskContainer();
- taskContainer.mContainers.remove(container);
+ taskContainer.mContainers.removeAll(containers);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
// In this way, we can keep track of the Task bounds until we no longer have any
// TaskFragment there.
- taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
+ taskContainer.mFinishedContainer.addAll(containers.stream().map(
+ TaskFragmentContainer::getTaskFragmentToken).toList());
// Cleanup any split references.
final List<SplitContainer> containersToRemove = new ArrayList<>();
for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
- if (container.equals(splitContainer.getSecondaryContainer())
- || container.equals(splitContainer.getPrimaryContainer())) {
+ if (containersToRemove.contains(splitContainer)) {
+ // Don't need to check because it has been in the remove list.
+ continue;
+ }
+ if (containers.stream().anyMatch(container ->
+ splitContainer.getPrimaryContainer().equals(container)
+ || splitContainer.getSecondaryContainer().equals(container))) {
containersToRemove.add(splitContainer);
}
}
@@ -1364,7 +1480,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Cleanup any dependent references.
for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
- containerToUpdate.removeContainerToFinishOnExit(container);
+ containerToUpdate.removeContainersToFinishOnExit(containers);
}
}
@@ -1444,26 +1560,54 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitContainer == null) {
return;
}
+
+ updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
+ }
+
+ /**
+ * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
+ * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
+ * are {@code null}, the {@link SplitAttributes} will be calculated with
+ * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ *
+ * @param splitContainer The {@link SplitContainer} to update
+ * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
+ * Otherwise, use the value calculated by
+ * {@link SplitPresenter#computeSplitAttributes(
+ * TaskContainer.TaskProperties, SplitRule, Pair)}
+ *
+ * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer,
+ @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) {
if (!isTopMostSplit(splitContainer)) {
// Skip position update - it isn't the topmost split.
- return;
+ return false;
}
if (splitContainer.getPrimaryContainer().isFinished()
|| splitContainer.getSecondaryContainer().isFinished()) {
// Skip position update - one or both containers are finished.
- return;
+ return false;
}
- final TaskContainer taskContainer = splitContainer.getTaskContainer();
- final SplitRule splitRule = splitContainer.getSplitRule();
- final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
- final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
- taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
- splitContainer.setSplitAttributes(splitAttributes);
+ if (splitAttributes == null) {
+ final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer()
+ .getTaskProperties();
+ final SplitRule splitRule = splitContainer.getSplitRule();
+ final SplitAttributes defaultSplitAttributes = splitContainer
+ .getDefaultSplitAttributes();
+ final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
+ splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule,
+ defaultSplitAttributes, minDimensionsPair);
+ }
+ splitContainer.updateCurrentSplitAttributes(splitAttributes);
if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
- return;
+ return true;
}
- mPresenter.updateSplitContainer(splitContainer, container, wct);
+ mPresenter.updateSplitContainer(splitContainer, wct);
+ return true;
}
/** Whether the given split is the topmost split in the Task. */
@@ -1559,7 +1703,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent());
final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
- placeholderRule, minDimensionsPair);
+ placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair);
if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
@@ -1614,7 +1758,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
// Only set trigger type if the launch happens in foreground.
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1637,12 +1782,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// The placeholder should remain after it was first shown.
return false;
}
- final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
- mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
+ mTransactionManager.getCurrentTransactionRecord()
+ .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1778,6 +1924,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
+ @VisibleForTesting
+ @Nullable
+ @GuardedBy("mLock")
+ SplitContainer getSplitContainer(@NonNull IBinder token) {
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+ for (SplitContainer container : containers) {
+ if (container.getToken().equals(token)) {
+ return container;
+ }
+ }
+ }
+ return null;
+ }
+
@Nullable
@GuardedBy("mLock")
TaskContainer getTaskContainer(int taskId) {
@@ -1920,6 +2081,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (!container.hasActivity(activityToken)
&& container.getTaskFragmentToken()
.equals(initialTaskFragmentToken)) {
+ if (ActivityClient.getInstance().isRequestedToLaunchInTaskFragment(
+ activityToken, initialTaskFragmentToken)) {
+ container.addPendingAppearedInRequestedTaskFragmentActivity(
+ activity);
+ }
+
// The onTaskFragmentInfoChanged callback containing this activity has
// not reached the client yet, so add the activity to the pending
// appeared activities.
@@ -1946,7 +2113,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
- transactionRecord.setOriginType(TRANSIT_OPEN);
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
activity);
// The WCT should be applied and merged to the activity launch transition.
@@ -2011,6 +2178,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// TODO(b/232042367): Consolidate the activity create handling so that we can handle
// cross-process the same as normal.
+ // Early return if the launching taskfragment is already been set.
+ if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
+ synchronized (mLock) {
+ mCurrentIntent = intent;
+ }
+ return super.onStartActivity(who, intent, options);
+ }
+
final Activity launchingActivity;
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
@@ -2036,7 +2211,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
- transactionRecord.setOriginType(TRANSIT_OPEN);
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
@@ -2053,6 +2228,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
+ // TODO(b/229680885): skip override launching TaskFragment token by split-rule
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
@@ -2156,30 +2332,4 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return configuration != null
&& configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
-
- @Override
- public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
- @NonNull IBinder token) {
- throw new UnsupportedOperationException(
- "setLaunchingActivityStack is not supported in API_VERSION=2");
- }
-
- @Override
- public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
- throw new UnsupportedOperationException(
- "finishActivityStacks is not supported in API_VERSION=2");
- }
-
- @Override
- public void invalidateTopVisibleSplitAttributes() {
- throw new UnsupportedOperationException(
- "invalidateTopVisibleSplitAttributes is not supported in API_VERSION=2");
- }
-
- @Override
- public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
- @NonNull SplitAttributes splitAttributes) {
- throw new UnsupportedOperationException(
- "updateSplitAttributes is not supported in API_VERSION=2");
- }
}
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 668a7d5aa9b6..040851181e92 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,6 +30,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
@@ -178,23 +179,22 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
primaryActivity, secondaryIntent);
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- minDimensionsPair);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ rule.getDefaultSplitAttributes(), minDimensionsPair);
+ final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
+ primaryActivity, primaryRelBounds, splitAttributes, null /* containerToAvoid */);
// Create new empty task fragment
final int taskId = primaryContainer.getTaskId();
final TaskFragmentContainer secondaryContainer = mController.newContainer(
secondaryIntent, primaryActivity, taskId);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
+ .getWindowingModeForSplitTaskFragment(secondaryRelBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
- primaryActivity.getActivityToken(), secondaryRectBounds,
- windowingMode);
+ primaryActivity.getActivityToken(), secondaryRelBounds, windowingMode);
updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
// Set adjacent to each other so that the containers below will be invisible.
@@ -225,13 +225,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- minDimensionsPair);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ rule.getDefaultSplitAttributes(), minDimensionsPair);
+ final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
+ primaryActivity, primaryRelBounds, splitAttributes, null /* containerToAvoid */);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
@@ -243,7 +243,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
containerToAvoid = curSecondaryContainer;
}
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
- secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid);
+ secondaryActivity, secondaryRelBounds, splitAttributes, containerToAvoid);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -260,23 +260,23 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
*/
private TaskFragmentContainer prepareContainerForActivity(
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
- @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes,
+ @NonNull Rect relBounds, @NonNull SplitAttributes splitAttributes,
@Nullable TaskFragmentContainer containerToAvoid) {
TaskFragmentContainer container = mController.getContainerWithActivity(activity);
final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
if (container == null || container == containerToAvoid) {
container = mController.newContainer(activity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(bounds);
+ .getWindowingModeForSplitTaskFragment(relBounds);
final IBinder reparentActivityToken = activity.getActivityToken();
createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
- bounds, windowingMode, reparentActivityToken);
+ relBounds, windowingMode, reparentActivityToken);
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
reparentActivityToken);
} else {
- resizeTaskFragmentIfRegistered(wct, container, bounds);
+ resizeTaskFragmentIfRegistered(wct, container, relBounds);
final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(bounds);
+ .getWindowingModeForSplitTaskFragment(relBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
@@ -300,9 +300,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
@NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
final TaskProperties taskProperties = getTaskProperties(launchingActivity);
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
@@ -319,11 +319,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
- primaryRectBounds);
+ primaryRelBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
- startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
- launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
+ startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRelBounds,
+ launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRelBounds,
activityIntent, activityOptions, rule, windowingMode, splitAttributes);
if (isPlaceholder) {
// When placeholder is launched in split, we should keep the focus on the primary.
@@ -334,11 +334,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
/**
* Updates the positions of containers in an existing split.
* @param splitContainer The split container to be updated.
- * @param updatedContainer The task fragment that was updated and caused this split update.
* @param wct WindowContainerTransaction that this update should be performed with.
*/
void updateSplitContainer(@NonNull SplitContainer splitContainer,
- @NonNull TaskFragmentContainer updatedContainer,
@NonNull WindowContainerTransaction wct) {
// Getting the parent configuration using the updated container - it will have the recent
// value.
@@ -348,31 +346,31 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (activity == null) {
return;
}
- final TaskProperties taskProperties = getTaskProperties(updatedContainer);
- final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
- final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+ final TaskContainer taskContainer = splitContainer.getTaskContainer();
+ final TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+ final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
- final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+ final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
// Whether the placeholder is becoming side-by-side with the primary from fullscreen.
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
&& secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
- && !secondaryRectBounds.isEmpty();
+ && !secondaryRelBounds.isEmpty();
// If the task fragments are not registered yet, the positions will be updated after they
// are created again.
- resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
- resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
+ resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRelBounds);
+ resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRelBounds);
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
splitAttributes);
if (isPlaceholderBecomingSplit) {
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- final TaskContainer taskContainer = updatedContainer.getTaskContainer();
final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
- primaryRectBounds);
+ primaryRelBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
@@ -387,10 +385,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// secondaryContainer could not be finished.
boolean isStacked = !shouldShowSplit(splitAttributes);
if (isStacked) {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken());
} else {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ setAdjacentTaskFragmentsWithRule(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
@@ -404,11 +401,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
- @Nullable Rect bounds) {
+ @Nullable Rect relBounds) {
if (container.getInfo() == null) {
return;
}
- resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
+ resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
}
private void updateTaskFragmentWindowingModeIfRegistered(
@@ -427,30 +424,30 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
- "Creating a task fragment that is not registered with controller.");
+ "Creating a TaskFragment that is not registered with controller.");
}
- container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
+ container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
super.createTaskFragment(wct, fragmentOptions);
}
@Override
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @Nullable Rect bounds) {
+ @Nullable Rect relBounds) {
TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
throw new IllegalStateException(
- "Resizing a task fragment that is not registered with controller.");
+ "Resizing a TaskFragment that is not registered with controller.");
}
- if (container.areLastRequestedBoundsEqual(bounds)) {
+ if (container.areLastRequestedBoundsEqual(relBounds)) {
// Return early if the provided bounds were already requested
return;
}
- container.setLastRequestedBounds(bounds);
- super.resizeTaskFragment(wct, fragmentToken, bounds);
+ container.setLastRequestedBounds(relBounds);
+ super.resizeTaskFragment(wct, fragmentToken, relBounds);
}
@Override
@@ -458,7 +455,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting windowing mode for a task fragment that is"
+ throw new IllegalStateException("Setting windowing mode for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -476,7 +473,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting animation params for a task fragment that is"
+ throw new IllegalStateException("Setting animation params for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -489,6 +486,64 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateAnimationParams(wct, fragmentToken, animationParams);
}
+ @Override
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
+ final TaskFragmentContainer primaryContainer = mController.getContainer(primary);
+ final TaskFragmentContainer secondaryContainer = mController.getContainer(secondary);
+ if (primaryContainer == null || secondaryContainer == null) {
+ throw new IllegalStateException("setAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (primaryContainer.isLastAdjacentTaskFragmentEqual(secondary, adjacentParams)
+ && secondaryContainer.isLastAdjacentTaskFragmentEqual(primary, adjacentParams)) {
+ // Return early if the same adjacent TaskFragments were already requested
+ return;
+ }
+
+ primaryContainer.setLastAdjacentTaskFragment(secondary, adjacentParams);
+ secondaryContainer.setLastAdjacentTaskFragment(primary, adjacentParams);
+ super.setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ @Override
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("clearAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastAdjacentTaskFragmentEqual(null /* fragmentToken*/, null /* params */)) {
+ // Return early if no adjacent TaskFragment was yet requested
+ return;
+ }
+
+ container.clearLastAdjacentTaskFragment();
+ super.clearAdjacentTaskFragments(wct, fragmentToken);
+ }
+
+ @Override
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ final TaskFragmentContainer container = mController.getContainer(primary);
+ if (container == null) {
+ throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastCompanionTaskFragmentEqual(secondary)) {
+ // Return early if the same companion TaskFragment was already requested
+ return;
+ }
+
+ container.setLastCompanionTaskFragment(secondary);
+ super.setCompanionTaskFragment(wct, primary, secondary);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
@@ -515,9 +570,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Expand the splitContainer if minimum dimensions are not satisfied.
final TaskContainer taskContainer = splitContainer.getTaskContainer();
final SplitAttributes splitAttributes = sanitizeSplitAttributes(
- taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
+ taskContainer.getTaskProperties(), splitContainer.getCurrentSplitAttributes(),
minDimensionsPair);
- splitContainer.setSplitAttributes(splitAttributes);
+ splitContainer.updateCurrentSplitAttributes(splitAttributes);
if (!shouldShowSplit(splitAttributes)) {
// If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
// bounds. Return failure to create a new SplitContainer which fills task bounds.
@@ -540,7 +595,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
- return shouldShowSplit(splitContainer.getSplitAttributes());
+ return shouldShowSplit(splitContainer.getCurrentSplitAttributes());
}
static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
@@ -549,12 +604,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
- @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
+ @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
+ @Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
- final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
if (calculator == null) {
if (!areDefaultConstraintsSatisfied) {
@@ -657,7 +712,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@VisibleForTesting
@NonNull
- Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
+ Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
@NonNull SplitAttributes splitAttributes) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
@@ -670,16 +725,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (!shouldShowSplit(computedSplitAttributes)) {
return new Rect();
}
+ final Rect bounds;
switch (position) {
case POSITION_START:
- return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
+ bounds = getPrimaryBounds(taskConfiguration, computedSplitAttributes,
+ foldingFeature);
+ break;
case POSITION_END:
- return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
+ bounds = getSecondaryBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
+ break;
case POSITION_FILL:
default:
- return new Rect();
+ bounds = new Rect();
}
+ // Convert to relative bounds in parent coordinate. This is to avoid flicker when the Task
+ // resized before organizer requests have been applied.
+ taskProperties.translateAbsoluteBoundsToRelativeBounds(bounds);
+ return bounds;
}
@NonNull
@@ -949,11 +1012,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
- return container.getTaskContainer().getTaskProperties();
- }
-
- @NonNull
TaskProperties getTaskProperties(@NonNull Activity activity) {
final TaskContainer taskContainer = mController.getTaskContainer(
mController.getTaskId(activity));
@@ -972,6 +1030,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
// TODO(b/190433398): Supply correct insets.
- return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
+ final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 03f4dc9c1167..4b15bb187035 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -108,12 +108,6 @@ class TaskContainer {
}
@NonNull
- Configuration getConfiguration() {
- // Make a copy in case the config is updated unexpectedly.
- return new Configuration(mConfiguration);
- }
-
- @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
@@ -157,7 +151,7 @@ class TaskContainer {
@WindowingMode
private int getWindowingMode() {
- return getConfiguration().windowConfiguration.getWindowingMode();
+ return mConfiguration.windowConfiguration.getWindowingMode();
}
/** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -220,10 +214,7 @@ class TaskContainer {
}
}
- /**
- * A wrapper class which contains the display ID and {@link Configuration} of a
- * {@link TaskContainer}
- */
+ /** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
@NonNull
@@ -243,6 +234,15 @@ class TaskContainer {
return mConfiguration;
}
+ /** Translates the given absolute bounds to relative bounds in this Task coordinate. */
+ void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
+ if (inOutBounds.isEmpty()) {
+ return;
+ }
+ final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+ inOutBounds.offset(-taskBounds.left, -taskBounds.top);
+ }
+
/**
* Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is
* associated with.
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 17814c65e791..60be9d16d749 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -37,8 +37,10 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/**
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
@@ -84,6 +86,12 @@ class TaskFragmentContainer {
@Nullable
private Intent mPendingAppearedIntent;
+ /**
+ * The activities that were explicitly requested to be launched in its current TaskFragment,
+ * but haven't been added to {@link #mInfo} yet.
+ */
+ final ArrayList<IBinder> mPendingAppearedInRequestedTaskFragmentActivities = new ArrayList<>();
+
/** Containers that are dependent on this one and should be completely destroyed on exit. */
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
new ArrayList<>();
@@ -116,6 +124,27 @@ class TaskFragmentContainer {
private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
/**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private IBinder mLastAdjacentTaskFragment;
+
+ /**
+ * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
+ * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
+
+ /**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
+ */
+ @Nullable
+ private IBinder mLastCompanionTaskFragment;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -124,6 +153,11 @@ class TaskFragmentContainer {
Runnable mAppearEmptyTimeout;
/**
+ * Whether this TaskFragment contains activities of another process/package.
+ */
+ private boolean mHasCrossProcessActivities;
+
+ /**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
@@ -243,7 +277,7 @@ class TaskFragmentContainer {
@NonNull
ActivityStack toActivityStack() {
- return new ActivityStack(collectNonFinishingActivities(), isEmpty());
+ return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
}
/** Adds the activity that will be reparented to this container. */
@@ -273,6 +307,8 @@ class TaskFragmentContainer {
void removePendingAppearedActivity(@NonNull IBinder activityToken) {
mPendingAppearedActivities.remove(activityToken);
+ // Also remove the activity from the mPendingInRequestedTaskFragmentActivities.
+ mPendingAppearedInRequestedTaskFragmentActivities.remove(activityToken);
}
@GuardedBy("mController.mLock")
@@ -387,10 +423,18 @@ class TaskFragmentContainer {
mAppearEmptyTimeout = null;
}
+ mHasCrossProcessActivities = false;
mInfo = info;
if (mInfo == null || mInfo.isEmpty()) {
return;
}
+
+ // Contains activities of another process if the activities size is not matched to the
+ // running activity count
+ if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
+ mHasCrossProcessActivities = true;
+ }
+
// Only track the pending Intent when the container is empty.
mPendingAppearedIntent = null;
if (mPendingAppearedActivities.isEmpty()) {
@@ -401,7 +445,7 @@ class TaskFragmentContainer {
for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
final IBinder activityToken = mPendingAppearedActivities.get(i);
if (infoActivities.contains(activityToken)) {
- mPendingAppearedActivities.remove(i);
+ removePendingAppearedActivity(activityToken);
}
}
}
@@ -436,10 +480,17 @@ class TaskFragmentContainer {
* Removes a container that should be finished when this container is finished.
*/
void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
+ removeContainersToFinishOnExit(Collections.singletonList(containerToRemove));
+ }
+
+ /**
+ * Removes container list that should be finished when this container is finished.
+ */
+ void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) {
if (mIsFinished) {
return;
}
- mContainersToFinishOnExit.remove(containerToRemove);
+ mContainersToFinishOnExit.removeAll(containersToRemove);
}
/**
@@ -478,6 +529,16 @@ class TaskFragmentContainer {
@GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
+ finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */);
+ }
+
+ /**
+ * Removes all activities that belong to this process and finishes other containers/activities
+ * configured to finish together.
+ */
+ void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
+ @NonNull WindowContainerTransaction wct, @NonNull SplitController controller,
+ boolean shouldRemoveRecord) {
if (!mIsFinished) {
mIsFinished = true;
if (mAppearEmptyTimeout != null) {
@@ -494,8 +555,10 @@ class TaskFragmentContainer {
// Cleanup the visuals
presenter.deleteTaskFragment(wct, getTaskFragmentToken());
- // Cleanup the records
- controller.removeContainer(this);
+ if (shouldRemoveRecord) {
+ // Cleanup the records
+ controller.removeContainer(this);
+ }
// Clean up task fragment information
mInfo = null;
}
@@ -570,30 +633,30 @@ class TaskFragmentContainer {
/**
* Checks if last requested bounds are equal to the provided value.
+ * The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
- boolean areLastRequestedBoundsEqual(@Nullable Rect bounds) {
- return (bounds == null && mLastRequestedBounds.isEmpty())
- || mLastRequestedBounds.equals(bounds);
+ boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
+ return (relBounds == null && mLastRequestedBounds.isEmpty())
+ || mLastRequestedBounds.equals(relBounds);
}
/**
* Updates the last requested bounds.
+ * The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
- void setLastRequestedBounds(@Nullable Rect bounds) {
- if (bounds == null) {
+ void setLastRequestedBounds(@Nullable Rect relBounds) {
+ if (relBounds == null) {
mLastRequestedBounds.setEmpty();
} else {
- mLastRequestedBounds.set(bounds);
+ mLastRequestedBounds.set(relBounds);
}
}
- @NonNull
- Rect getLastRequestedBounds() {
- return mLastRequestedBounds;
- }
-
/**
* Checks if last requested windowing mode is equal to the provided value.
+ * @see WindowContainerTransaction#setWindowingMode
*/
boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
return mLastRequestedWindowingMode == windowingMode;
@@ -601,6 +664,7 @@ class TaskFragmentContainer {
/**
* Updates the last requested windowing mode.
+ * @see WindowContainerTransaction#setWindowingMode
*/
void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
mLastRequestedWindowingMode = windowingModes;
@@ -608,6 +672,7 @@ class TaskFragmentContainer {
/**
* Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
boolean areLastRequestedAnimationParamsEqual(
@NonNull TaskFragmentAnimationParams animationParams) {
@@ -616,11 +681,94 @@ class TaskFragmentContainer {
/**
* Updates the last requested {@link TaskFragmentAnimationParams}.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
mLastAnimationParams = animationParams;
}
+ /**
+ * Checks if last requested adjacent TaskFragment token and params are equal to the provided
+ * values.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
+ && Objects.equals(mLastAdjacentParams, params);
+ }
+
+ /**
+ * Updates the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ */
+ void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
+ @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ mLastAdjacentTaskFragment = fragmentToken;
+ mLastAdjacentParams = params;
+ }
+
+ /**
+ * Clears the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ void clearLastAdjacentTaskFragment() {
+ final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
+ ? mController.getContainer(mLastAdjacentTaskFragment)
+ : null;
+ mLastAdjacentTaskFragment = null;
+ mLastAdjacentParams = null;
+ if (lastAdjacentTaskFragment != null) {
+ // Clear the previous adjacent TaskFragment as well.
+ lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
+ }
+ }
+
+ /**
+ * Checks if last requested companion TaskFragment token is equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
+ return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
+ }
+
+ /**
+ * Updates the last requested companion TaskFragment token.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
+ mLastCompanionTaskFragment = fragmentToken;
+ }
+
+ /**
+ * Adds the pending appeared activity that has requested to be launched in this task fragment.
+ * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
+ */
+ void addPendingAppearedInRequestedTaskFragmentActivity(Activity activity) {
+ final IBinder activityToken = activity.getActivityToken();
+ if (hasActivity(activityToken)) {
+ return;
+ }
+ mPendingAppearedInRequestedTaskFragmentActivities.add(activity.getActivityToken());
+ }
+
+ /**
+ * Checks if the given activity has requested to be launched in this task fragment.
+ * @see #addPendingAppearedInRequestedTaskFragmentActivity
+ */
+ boolean isActivityInRequestedTaskFragment(IBinder activityToken) {
+ if (mInfo != null && mInfo.getActivitiesRequestedInTaskFragment().contains(activityToken)) {
+ return true;
+ }
+ return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
+ }
+
+ /** Whether contains activities of another process */
+ boolean hasCrossProcessActivities() {
+ return mHasCrossProcessActivities;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
index 0071fea41aa8..396956e56bb5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -16,12 +16,12 @@
package androidx.window.extensions.embedding;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_NONE;
import android.os.IBinder;
-import android.view.WindowManager.TransitionType;
import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer.TaskFragmentTransitionType;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -122,8 +122,8 @@ class TransactionManager {
* @see #setOriginType(int)
* @see #getTransactionTransitionType()
*/
- @TransitionType
- private int mOriginType = TRANSIT_NONE;
+ @TaskFragmentTransitionType
+ private int mOriginType = TASK_FRAGMENT_TRANSIT_NONE;
TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
mTaskFragmentTransactionToken = taskFragmentTransactionToken;
@@ -136,12 +136,12 @@ class TransactionManager {
}
/**
- * Sets the {@link TransitionType} that triggers this transaction. If there are multiple
- * calls, only the first call will be respected as the "origin" type.
+ * Sets the {@link TaskFragmentTransitionType} that triggers this transaction. If there are
+ * multiple calls, only the first call will be respected as the "origin" type.
*/
- void setOriginType(@TransitionType int type) {
+ void setOriginType(@TaskFragmentTransitionType int type) {
ensureCurrentTransaction();
- if (mOriginType != TRANSIT_NONE) {
+ if (mOriginType != TASK_FRAGMENT_TRANSIT_NONE) {
// Skip if the origin type has already been set.
return;
}
@@ -188,14 +188,16 @@ class TransactionManager {
}
/**
- * Gets the {@link TransitionType} that we will request transition with for the
+ * Gets the {@link TaskFragmentTransitionType} that we will request transition with for the
* current {@link WindowContainerTransaction}.
*/
@VisibleForTesting
- @TransitionType
+ @TaskFragmentTransitionType
int getTransactionTransitionType() {
- // Use TRANSIT_CHANGE as default if there is not opening/closing window.
- return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
+ // Use TASK_FRAGMENT_TRANSIT_CHANGE as default if there is not opening/closing window.
+ return mOriginType != TASK_FRAGMENT_TRANSIT_NONE
+ ? mOriginType
+ : TASK_FRAGMENT_TRANSIT_CHANGE;
}
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 459ec9f89c4a..a069ac7256d6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -177,6 +177,7 @@ public class EmbeddingTestUtils {
1,
isVisible,
Collections.singletonList(activity.getActivityToken()),
+ new ArrayList<>(),
new Point(),
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index bbb454d31c38..dd087e8eb7c9 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -124,7 +124,7 @@ public class JetpackTaskFragmentOrganizerTest {
private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
- false /* isVisible */, new ArrayList<>(), new Point(),
+ false /* isVisible */, new ArrayList<>(), new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
false /* isClearedForReorderActivityToFront */, new Point());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 0bf0bc85b511..cbdc262c0594 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -20,13 +20,13 @@ import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -67,6 +67,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import android.annotation.NonNull;
import android.app.Activity;
@@ -82,6 +83,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
@@ -100,12 +102,14 @@ import androidx.window.extensions.layout.WindowLayoutInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -275,7 +279,8 @@ public class SplitControllerTest {
assertNotNull(tf);
assertNotNull(taskContainer);
- assertEquals(TASK_BOUNDS, taskContainer.getConfiguration().windowConfiguration.getBounds());
+ assertEquals(TASK_BOUNDS, taskContainer.getTaskProperties().getConfiguration()
+ .windowConfiguration.getBounds());
}
@Test
@@ -288,7 +293,7 @@ public class SplitControllerTest {
doReturn(true).when(tf).isEmpty();
doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
mActivity, false /* isOnCreated */);
- doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
+ doNothing().when(mSplitPresenter).updateSplitContainer(any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -341,7 +346,7 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any());
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
@@ -349,7 +354,7 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
+ verify(mSplitPresenter).updateSplitContainer(splitContainer, mTransaction);
}
@Test
@@ -366,14 +371,14 @@ public class SplitControllerTest {
doReturn(false).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
- verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any());
// Update the split when the Task is visible.
doReturn(true).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
- taskFragmentContainer, mTransaction);
+ mTransaction);
}
@Test
@@ -695,7 +700,7 @@ public class SplitControllerTest {
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
- assertFalse(result);
+ assertTrue(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
any(), anyBoolean());
}
@@ -729,7 +734,7 @@ public class SplitControllerTest {
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
- assertFalse(result);
+ assertTrue(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
any(), anyBoolean());
}
@@ -803,7 +808,7 @@ public class SplitControllerTest {
final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity);
- assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
false /* isOnReparent */));
}
@@ -939,7 +944,7 @@ public class SplitControllerTest {
boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
- assertFalse(result);
+ assertTrue(result);
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
@@ -1009,6 +1014,25 @@ public class SplitControllerTest {
}
@Test
+ public void testFindActivityBelow() {
+ // Create a container with two activities
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pendingAppearedActivity = createMockActivity();
+ container.addPendingAppearedActivity(pendingAppearedActivity);
+
+ // Ensure the activity below matches
+ assertEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
+
+ // Ensure that the activity look up won't search for the in-process activities and should
+ // IPC to WM core to get the activity below. It should be `null` for this mock test.
+ spyOn(container);
+ doReturn(true).when(container).hasCrossProcessActivities();
+ assertNotEquals(mActivity,
+ mSplitController.findActivityBelow(pendingAppearedActivity));
+ }
+
+ @Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController)
.getTaskFragmentTokenFromActivityClientRecord(mActivity);
@@ -1139,7 +1163,7 @@ public class SplitControllerTest {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final IBinder errorToken = new Binder();
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- final int opType = HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+ final int opType = OP_TYPE_CREATE_TASK_FRAGMENT;
final Exception exception = new SecurityException("test");
final Bundle errorBundle = TaskFragmentOrganizer.putErrorInfoInBundle(exception, info,
opType);
@@ -1322,6 +1346,125 @@ public class SplitControllerTest {
verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any());
}
+ @Test
+ public void testFinishActivityStacks_emptySet_earlyReturn() {
+ mSplitController.finishActivityStacks(Collections.emptySet());
+
+ verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt());
+ }
+
+ @Test
+ public void testFinishActivityStacks_invalidStacks_earlyReturn() {
+ mSplitController.finishActivityStacks(Collections.singleton(new Binder()));
+
+ verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt());
+ }
+
+ @Test
+ public void testFinishActivityStacks_finishSingleActivityStack() {
+ TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
+ tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
+
+ List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
+ .mContainers;
+
+ assertEquals(containers.get(0), tf);
+
+ mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
+
+ verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
+ assertTrue(containers.isEmpty());
+ }
+
+ @Test
+ public void testFinishActivityStacks_finishActivityStacksInOrder() {
+ TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID);
+ TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID);
+ bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
+ topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
+
+ List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
+ .mContainers;
+
+ assertEquals(containers.size(), 2);
+
+ Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
+ topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
+
+ mSplitController.finishActivityStacks(activityStackTokens);
+
+ ArgumentCaptor<IBinder> argumentCaptor = ArgumentCaptor.forClass(IBinder.class);
+
+ verify(mSplitPresenter, times(2)).deleteTaskFragment(any(), argumentCaptor.capture());
+
+ List<IBinder> fragmentTokens = argumentCaptor.getAllValues();
+ assertEquals("The ActivityStack must be deleted from the lowest z-order "
+ + "regardless of the order in ActivityStack set",
+ bottomTf.getTaskFragmentToken(), fragmentTokens.get(0));
+ assertEquals("The ActivityStack must be deleted from the lowest z-order "
+ + "regardless of the order in ActivityStack set",
+ topTf.getTaskFragmentToken(), fragmentTokens.get(1));
+
+ assertTrue(containers.isEmpty());
+ }
+
+ @Test
+ public void testUpdateSplitAttributes_invalidSplitContainerToken_earlyReturn() {
+ mSplitController.updateSplitAttributes(new Binder(), SPLIT_ATTRIBUTES);
+
+ verify(mTransactionManager, never()).startNewTransaction();
+ }
+
+ @Test
+ public void testUpdateSplitAttributes_nullParams_throwException() {
+ assertThrows(NullPointerException.class,
+ () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES));
+
+ final SplitContainer splitContainer = mock(SplitContainer.class);
+ final IBinder token = new Binder();
+ doReturn(token).when(splitContainer).getToken();
+ doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token));
+
+ assertThrows(NullPointerException.class,
+ () -> mSplitController.updateSplitAttributes(token, null));
+ }
+
+ @Test
+ public void testUpdateSplitAttributes_doNotNeedToUpdateSplitContainer_doNotApplyTransaction() {
+ final SplitContainer splitContainer = mock(SplitContainer.class);
+ final IBinder token = new Binder();
+ doReturn(token).when(splitContainer).getToken();
+ doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token));
+ doReturn(false).when(mSplitController).updateSplitContainerIfNeeded(
+ eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES));
+ TransactionManager.TransactionRecord testRecord =
+ mock(TransactionManager.TransactionRecord.class);
+ doReturn(testRecord).when(mTransactionManager).startNewTransaction();
+
+ mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES);
+
+ verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES));
+ verify(testRecord).abort();
+ }
+
+ @Test
+ public void testUpdateSplitAttributes_splitContainerUpdated_updateAttrs() {
+ final SplitContainer splitContainer = mock(SplitContainer.class);
+ final IBinder token = new Binder();
+ doReturn(token).when(splitContainer).getToken();
+ doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token));
+ doReturn(true).when(mSplitController).updateSplitContainerIfNeeded(
+ eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES));
+ TransactionManager.TransactionRecord testRecord =
+ mock(TransactionManager.TransactionRecord.class);
+ doReturn(testRecord).when(mTransactionManager).startNewTransaction();
+
+ mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES);
+
+ verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES));
+ verify(testRecord).apply(eq(false));
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index a288fd6a3067..83301564d7a4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -66,13 +66,16 @@ import android.graphics.Color;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Size;
+import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -102,7 +105,6 @@ import java.util.ArrayList;
@RunWith(AndroidJUnit4.class)
public class SplitPresenterTest {
- @Mock
private Activity mActivity;
@Mock
private Resources mActivityResources;
@@ -147,13 +149,13 @@ public class SplitPresenterTest {
mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS);
assertTrue(container.areLastRequestedBoundsEqual(TASK_BOUNDS));
- verify(mTransaction).setBounds(any(), eq(TASK_BOUNDS));
+ verify(mTransaction).setRelativeBounds(any(), eq(TASK_BOUNDS));
// No request to set the same bounds.
clearInvocations(mTransaction);
mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS);
- verify(mTransaction, never()).setBounds(any(), any());
+ verify(mTransaction, never()).setRelativeBounds(any(), any());
}
@Test
@@ -175,6 +177,64 @@ public class SplitPresenterTest {
}
@Test
+ public void testSetAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ verify(mTransaction).setAdjacentTaskFragments(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ verify(mTransaction, never()).setAdjacentTaskFragments(any(), any(), any());
+ }
+
+ @Test
+ public void testClearAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ // No request to clear as it is not set by default.
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction).clearAdjacentTaskFragments(container0.getTaskFragmentToken());
+
+ // No request to clear on either of the previous cleared TasKFragments.
+ clearInvocations(mTransaction);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+ }
+
+ @Test
+ public void testSetCompanionTaskFragment() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+ verify(mTransaction).setCompanionTaskFragment(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).setCompanionTaskFragment(any(), any());
+ }
+
+ @Test
public void testUpdateAnimationParams() {
final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
@@ -194,7 +254,7 @@ public class SplitPresenterTest {
OP_TYPE_SET_ANIMATION_PARAMS)
.setAnimationParams(animationParams)
.build();
- verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(),
+ verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(),
expectedOperation);
assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams));
@@ -203,7 +263,7 @@ public class SplitPresenterTest {
mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
animationParams);
- verify(mTransaction, never()).setTaskFragmentOperation(any(), any());
+ verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
}
@Test
@@ -225,31 +285,119 @@ public class SplitPresenterTest {
}
@Test
- public void testGetBoundsForPosition_expandContainers() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ public void testGetRelBoundsForPosition_expandContainers() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
.build();
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetRelBoundsForPosition_expandContainers_isRelativeToParent() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties(
+ new Rect(100, 100, 500, 1000));
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+ .build();
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
+
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ }
+
+ @Test
+ public void testGetRelBoundsForPosition_splitVertically() {
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+ false /* splitHorizontally */);
+ final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+ false /* splitHorizontally */);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
+ SplitAttributes splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+ .build();
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ assertEquals("Task bounds must be reported.",
+ new Rect(),
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT)
+ .build();
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+ splitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .build();
+ // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE.
+ taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+
+ assertEquals("Secondary bounds must be reported.",
+ secondaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
+
+ assertEquals("Primary bounds must be reported.",
+ primaryBounds,
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
- public void testGetBoundsForPosition_splitVertically() {
+ public void testGetRelBoundsForPosition_splitVertically_isRelativeToParent() {
+ // Calculate based on TASK_BOUNDS.
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
false /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
false /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+
+ // Offset TaskBounds to 100, 100. The returned rel bounds shouldn't be affected.
+ final Rect taskBounds = new Rect(TASK_BOUNDS);
+ taskBounds.offset(100, 100);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties(taskBounds);
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
@@ -257,14 +405,15 @@ public class SplitPresenterTest {
assertEquals("Primary bounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
@@ -273,14 +422,15 @@ public class SplitPresenterTest {
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Primary bounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
@@ -291,23 +441,24 @@ public class SplitPresenterTest {
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Primary bounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
- public void testGetBoundsForPosition_splitHorizontally() {
+ public void testGetRelBoundsForPosition_splitHorizontally() {
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
true /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
true /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
.setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
@@ -315,14 +466,15 @@ public class SplitPresenterTest {
assertEquals("Primary bounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
@@ -331,23 +483,24 @@ public class SplitPresenterTest {
assertEquals("Secondary bounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Primary bounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
- public void testGetBoundsForPosition_useHingeFallback() {
+ public void testGetRelBoundsForPosition_useHingeFallback() {
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
false /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
false /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
SplitAttributes.SplitType.RatioSplitType.splitEqually()
@@ -360,14 +513,15 @@ public class SplitPresenterTest {
assertEquals("PrimaryBounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("SecondaryBounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
// Hinge is reported, but the host task is in multi-window mode. Still use fallback
// splitType.
@@ -378,14 +532,15 @@ public class SplitPresenterTest {
assertEquals("PrimaryBounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("SecondaryBounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
// Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match
// folding area orientation. Still use fallback splitType.
@@ -396,19 +551,20 @@ public class SplitPresenterTest {
assertEquals("PrimaryBounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("SecondaryBounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
- public void testGetBoundsForPosition_fallbackToExpandContainers() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ public void testGetRelBoundsForPosition_fallbackToExpandContainers() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
new SplitAttributes.SplitType.ExpandContainersSplitType()
@@ -417,19 +573,20 @@ public class SplitPresenterTest {
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
- public void testGetBoundsForPosition_useHingeSplitType() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ public void testGetRelBoundsForPosition_useHingeSplitType() {
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
new SplitAttributes.SplitType.ExpandContainersSplitType()
@@ -454,14 +611,15 @@ public class SplitPresenterTest {
assertEquals("PrimaryBounds must be reported.",
primaryBounds,
- mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_START, taskProperties,
+ splitAttributes));
assertEquals("SecondaryBounds must be reported.",
secondaryBounds,
- mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
assertEquals("Task bounds must be reported.",
new Rect(),
- mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+ mPresenter.getRelBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
}
@Test
@@ -481,13 +639,13 @@ public class SplitPresenterTest {
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter, never()).expandTaskFragment(any(), any());
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
secondaryTf.setInfo(mTransaction,
createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -497,7 +655,7 @@ public class SplitPresenterTest {
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
clearInvocations(mPresenter);
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -539,37 +697,50 @@ public class SplitPresenterTest {
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
final Pair<Size, Size> minDimensionsPair = new Pair<>(
new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, minDimensionsPair));
+ splitPairRule, SPLIT_ATTRIBUTES, minDimensionsPair));
taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
TASK_BOUNDS.bottom + 1));
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
- .setSplitType(
- new SplitAttributes.SplitType.HingeSplitType(
- SplitAttributes.SplitType.RatioSplitType.splitEqually()
- )
- ).build();
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()))
+ .build();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
params -> splitAttributes;
mController.setSplitAttributesCalculator(calculator);
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
+ }
+
+ @Test
+ public void testGetTaskWindowMetrics() {
+ final Configuration taskConfig = new Configuration();
+ taskConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ taskConfig.densityDpi = 123;
+ final TaskContainer.TaskProperties taskProperties = new TaskContainer.TaskProperties(
+ DEFAULT_DISPLAY, taskConfig);
+ doReturn(taskProperties).when(mPresenter).getTaskProperties(mActivity);
+
+ final WindowMetrics windowMetrics = mPresenter.getTaskWindowMetrics(mActivity);
+ assertEquals(TASK_BOUNDS, windowMetrics.getBounds());
+ assertEquals(123 * DisplayMetrics.DENSITY_DEFAULT_SCALE,
+ windowMetrics.getDensity(), 0f);
}
private Activity createMockActivity() {
@@ -581,12 +752,17 @@ public class SplitPresenterTest {
doReturn(activityConfig).when(mActivityResources).getConfiguration();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(mock(IBinder.class)).when(activity).getActivityToken();
+ doReturn(TASK_ID).when(activity).getTaskId();
return activity;
}
- private static TaskContainer.TaskProperties getTaskProperty() {
+ private static TaskContainer.TaskProperties getTaskProperties() {
+ return getTaskProperties(TASK_BOUNDS);
+ }
+
+ private static TaskContainer.TaskProperties getTaskProperties(@NonNull Rect taskBounds) {
final Configuration configuration = new Configuration();
- configuration.windowConfiguration.setBounds(TASK_BOUNDS);
+ configuration.windowConfiguration.setBounds(taskBounds);
return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
index 62006bd51399..459b6d2c31f9 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
@@ -16,9 +16,9 @@
package androidx.window.extensions.embedding;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
@@ -86,27 +86,29 @@ public class TransactionManagerTest {
@Test
public void testSetTransactionOriginType() {
- // Return TRANSIT_CHANGE if there is no trigger type set.
+ // Return TASK_FRAGMENT_TRANSIT_CHANGE if there is no trigger type set.
TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
- assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+ assertEquals(TASK_FRAGMENT_TRANSIT_CHANGE,
+ transactionRecord.getTransactionTransitionType());
// Return the first set type.
mTransactionManager.getCurrentTransactionRecord().abort();
transactionRecord = mTransactionManager.startNewTransaction();
- transactionRecord.setOriginType(TRANSIT_OPEN);
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_OPEN);
- assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+ assertEquals(TASK_FRAGMENT_TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
- transactionRecord.setOriginType(TRANSIT_CLOSE);
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
- assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+ assertEquals(TASK_FRAGMENT_TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
// Reset when #startNewTransaction().
transactionRecord.abort();
transactionRecord = mTransactionManager.startNewTransaction();
- assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+ assertEquals(TASK_FRAGMENT_TRANSIT_CHANGE,
+ transactionRecord.getTransactionTransitionType());
}
@Test
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 8881be7326a9..36d3313a9f3b 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -21,5 +21,6 @@
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
index 7475abac4695..b2d59396d58d 100644
--- a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
+++ b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
@@ -18,6 +18,20 @@
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <set>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleX"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleY"
+ android:valueTo="1.0"
+ android:valueType="floatType"/>
+ </set>
+ </item>
<item android:state_focused="true">
<set>
<objectAnimator
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
index ce8640df0093..67467bbc72ae 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@color/tv_pip_menu_icon_unfocused" />
+ <item android:color="@color/tv_window_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_window_menu_close_icon_bg.xml
index 4f5e63dac5c0..4182bfeefa1b 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ 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.
@@ -16,6 +16,6 @@
-->
<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" />
+ android:color="@color/tv_window_menu_close_icon_bg_focused" />
+ <item android:color="@color/tv_window_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_window_menu_icon.xml
index 275870450493..45205d2a7138 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ 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.
@@ -16,8 +16,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_icon_focused" />
+ android:color="@color/tv_window_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" />
+ android:color="@color/tv_window_menu_icon_disabled" />
+ <item android:color="@color/tv_window_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_window_menu_icon_bg.xml
index 6cbf66f00df7..1bd26e1d6583 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
@@ -16,6 +16,6 @@
-->
<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" />
+ android:color="@color/tv_window_menu_icon_bg_focused" />
+ <item android:color="@color/tv_window_menu_icon_bg_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
deleted file mode 100644
index 1938f4562e97..000000000000
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.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.
--->
-<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
index 846fdb3e8a58..7085a2c72c86 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+ android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration">
<item android:state_activated="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_corner_radius" />
diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml
new file mode 100644
index 000000000000..a348b148afb4
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_focus.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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/tv_window_menu_icon_size"
+ android:height="@dimen/tv_window_menu_icon_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M17,4h3c1.1,0 2,0.9 2,2v2h-2L20,6h-3L17,4zM4,8L4,6h3L7,4L4,4c-1.1,0 -2,0.9 -2,2v2h2zM20,16v2h-3v2h3c1.1,0 2,-0.9 2,-2v-2h-2zM7,18L4,18v-2L2,16v2c0,1.1 0.9,2 2,2h3v-2zM18,8L6,8v8h12L18,8z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.xml
new file mode 100644
index 000000000000..c5d54c5fa4f2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_split_menu_ic_swap.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="@dimen/tv_window_menu_icon_size"
+ android:height="@dimen/tv_window_menu_icon_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
new file mode 100644
index 000000000000..4c28e519afa7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/tv_window_menu_button_radius" />
+ <solid android:color="@android:color/white" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 298ad3025b00..8d1da0f7ad1b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -63,11 +63,11 @@
android:tint="@color/bubbles_icon_tint"/>
<TextView
+ android:id="@+id/bubble_manage_menu_dont_bubble_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
- android:text="@string/bubbles_dont_bubble_conversation" />
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 7a3ee23d8cdc..dcce4698c252 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,92 +16,46 @@
-->
<!-- Layout for TvPipMenuView -->
<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:gravity="center|top">
+ android:id="@+id/tv_pip_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|top">
<!-- Matches the PiP app content -->
- <View
+ <FrameLayout
android:id="@+id/tv_pip"
android:layout_width="0dp"
android:layout_height="0dp"
- android:alpha="0"
- android:background="@color/tv_pip_menu_background"
android:layout_marginTop="@dimen/pip_menu_outer_space"
android:layout_marginStart="@dimen/pip_menu_outer_space"
- android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
+ android:layout_marginEnd="@dimen/pip_menu_outer_space">
- <ScrollView
- android:id="@+id/tv_pip_menu_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none"
- android:visibility="gone"/>
+ <View
+ android:id="@+id/tv_pip_menu_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_background"
+ android:alpha="0"/>
- <HorizontalScrollView
- android:id="@+id/tv_pip_menu_horizontal_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none">
+ <View
+ android:id="@+id/tv_pip_menu_dim_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_dim_layer"
+ android:alpha="0"/>
- <LinearLayout
+ <com.android.internal.widget.RecyclerView
android:id="@+id/tv_pip_menu_action_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- 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_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. -->
-
- <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" />
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- </LinearLayout>
- </HorizontalScrollView>
+ android:layout_gravity="center"
+ android:padding="@dimen/pip_menu_button_start_end_offset"
+ android:clipToPadding="false"
+ android:alpha="0"
+ android:contentDescription="@string/a11y_pip_menu_entered"/>
+ </FrameLayout>
+ <!-- Frame around the content, just overlapping the corners to make them round -->
<View
android:id="@+id/tv_pip_border"
android:layout_width="0dp"
@@ -111,8 +65,9 @@
android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
+ <!-- Temporarily extending the background to show an edu text hint for opening the menu -->
<FrameLayout
- android:id="@+id/tv_pip_menu_edu_text_container"
+ android:id="@+id/tv_pip_menu_edu_text_drawer_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tv_pip"
@@ -120,23 +75,10 @@
android:layout_alignStart="@+id/tv_pip"
android:layout_alignEnd="@+id/tv_pip"
android:background="@color/tv_pip_menu_background"
- android:clipChildren="true">
-
- <TextView
- android:id="@+id/tv_pip_menu_edu_text"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/pip_menu_edu_text_view_height"
- android:layout_gravity="bottom|center"
- android:gravity="center"
- android:paddingBottom="@dimen/pip_menu_border_width"
- android:text="@string/pip_edu_text"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:marqueeRepeatLimit="1"
- android:scrollHorizontally="true"
- android:textAppearance="@style/TvPipEduText"/>
- </FrameLayout>
+ android:paddingBottom="@dimen/pip_menu_border_width"
+ android:paddingTop="@dimen/pip_menu_border_width"/>
+ <!-- Frame around the PiP content + edu text hint - used to highlight open menu -->
<View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
@@ -144,6 +86,17 @@
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
+ <!-- Move menu -->
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_pip_menu_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:src="@drawable/pip_ic_close_white"
+ android:visibility="gone"
+ android:text="@string/a11y_action_pip_move_done" />
+
<ImageView
android:id="@+id/tv_pip_menu_arrow_up"
android:layout_width="@dimen/pip_menu_arrow_size"
@@ -151,6 +104,7 @@
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:alpha="0"
+ android:contentDescription="@string/a11y_action_pip_move_up"
android:elevation="@dimen/pip_menu_arrow_elevation"
android:src="@drawable/pip_ic_move_up" />
@@ -161,6 +115,7 @@
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:alpha="0"
+ android:contentDescription="@string/a11y_action_pip_move_right"
android:elevation="@dimen/pip_menu_arrow_elevation"
android:src="@drawable/pip_ic_move_right" />
@@ -171,6 +126,7 @@
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:alpha="0"
+ android:contentDescription="@string/a11y_action_pip_move_down"
android:elevation="@dimen/pip_menu_arrow_elevation"
android:src="@drawable/pip_ic_move_down" />
@@ -181,6 +137,7 @@
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:alpha="0"
+ android:contentDescription="@string/a11y_action_pip_move_left"
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
deleted file mode 100644
index db96d8de4094..000000000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ /dev/null
@@ -1,40 +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.
--->
-<!-- Layout for TvPipMenuActionButton -->
-<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:padding="@dimen/pip_menu_button_margin"
- android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
- android:focusable="true">
-
- <View android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:duplicateParentState="true"
- android:background="@drawable/tv_pip_button_bg"/>
-
- <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_split_menu_view.xml b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
new file mode 100644
index 000000000000..e0fa59c9f157
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_split_menu_view.xml
@@ -0,0 +1,125 @@
+<!--
+ ~ 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.
+ -->
+<!-- Layout for TvSplitMenuView -->
+<com.android.wm.shell.splitscreen.tv.TvSplitMenuView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+
+ <LinearLayout
+ android:id="@+id/tv_split_main_menu"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_split_main_menu_focus_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/tv_split_menu_ic_focus" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_split_main_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_split_menu_swap_stages"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/tv_split_menu_ic_swap" />
+
+ <LinearLayout
+ android:id="@+id/tv_split_side_menu"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_split_side_menu_focus_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/tv_split_menu_ic_focus" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <com.android.wm.shell.common.TvWindowMenuActionButton
+ android:id="@+id/tv_split_side_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+</com.android.wm.shell.splitscreen.tv.TvSplitMenuView>
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
new file mode 100644
index 000000000000..b2ac85b018be
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+<!-- Layout for TvWindowMenuActionButton -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/button"
+ android:layout_width="@dimen/tv_window_menu_button_size"
+ android:layout_height="@dimen/tv_window_menu_button_size"
+ android:padding="@dimen/tv_window_menu_button_margin"
+ android:duplicateParentState="true"
+ android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
+ android:focusable="true">
+
+ <View android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:background="@drawable/tv_window_button_bg"/>
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="@dimen/tv_window_menu_icon_size"
+ android:layout_height="@dimen/tv_window_menu_icon_size"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:tint="@color/tv_window_menu_icon" />
+</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 2476f65c7e5b..0897712e2037 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Gaan by verdeelde skerm in"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Kieslys"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Prent-in-prent-kieslys"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in beeld-in-beeld"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Speel"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Skermverdeler"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Volskerm links"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Links 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bo 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Volskerm onder"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Verdeel links"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Verdeel regs"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Verdeel bo"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Verdeel onder"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Beweeg na regs onder"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-instellings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Maak borrel toe"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Moenie dat gesprek \'n borrel word nie"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klets met borrels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nuwe gesprekke verskyn as swerwende ikone, of borrels Tik op borrel om dit oop te maak. Sleep om dit te skuif."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Borrel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Bestuur"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Borrel is toegemaak."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tik om hierdie program te herbegin en maak volskerm oop."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tik om hierdie program te herbegin vir ’n beter aansig."</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_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep ’n ander program in vir verdeelde skerm"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Jy kan die app herbegin sodat dit beter op jou skerm lyk, maar jy sal dalk jou vordering of enige ongestoorde veranderinge verloor"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Appikoon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Rekenaarmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Sweef"</string>
+ <string name="select_text" msgid="5139083974039906583">"Kies"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skermskoot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Maak toe"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
index c87bec093cca..2254fc9beb11 100644
--- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Beeld-in-beeld"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Maak 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>
+ <string name="pip_move" msgid="158770205886688553">"Skuif"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Vou uit"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Vou in"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Dubbeldruk "<annotation icon="home_icon">"TUIS"</annotation>" vir kontroles"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Prent-in-prent-kieslys"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Skuif links"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Skuif regs"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Skuif op"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Skuif af"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klaar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index f0c391cd6b99..bc58e202d80d 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -22,7 +22,8 @@
<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_menu_accessibility_title" msgid="8129016817688656249">"የሥዕል-ላይ-ሥዕል ምናሌ"</string>
+ <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በሥዕል-ላይ-ሥዕል ውስጥ ነው"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ይህን ባህሪ እንዲጠቀም ካልፈለጉ ቅንብሮችን ለመክፈት መታ ያድርጉና ያጥፉት።"</string>
<string name="pip_play" msgid="3496151081459417097">"አጫውት"</string>
<string name="pip_pause" msgid="690688849510295232">"ባለበት አቁም"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
<string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"የተከፈለ የማያ ገጽ ከፋይ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ወደ ግራ ከፋፍል"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ወደ ቀኝ ከፋፍል"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ወደ ላይ ከፋፍል"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"ወደ ታች ከፋፍል"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ባለአንድ እጅ ሁነታን በመጠቀም ላይ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ባለአንድ እጅ ሁነታ ጀምር"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ታችኛውን ቀኝ ያንቀሳቅሱ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"የ<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ቅንብሮች"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"አረፋን አሰናብት"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ እና ወደ ሙሉ ማያ ገጽ ይሂዱ።"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ለተሻለ ዕይታ ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ።"</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_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ለተሻለ ዕይታ እንደገና ይጀመር?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"በማያ ገጽዎ ላይ የተሻለ ሆኖ እንዲታይ መተግበሪያውን እንደገና ማስጀመር ይችላሉ ነገር ግን የደረሱበትን የሂደት ደረጃ ወይም ማናቸውንም ያልተቀመጡ ለውጦች ሊያጡ ይችላሉ"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ይቅር"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
+ <string name="select_text" msgid="5139083974039906583">"ምረጥ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገጽ እይታ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index d23353858de6..a6be57889a4e 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -17,12 +17,18 @@
<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_close" msgid="2955969519031223530">"ዝጋ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ውሰድ"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ለመቆጣጠሪያዎች "<annotation icon="home_icon">"መነሻ"</annotation>"ን ሁለቴ ይጫኑ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"የሥዕል-ላይ-ሥዕል ምናሌ።"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ወደ ግራ ውሰድ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ወደ ቀኝ ውሰድ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ወደ ላይ ውሰድ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ወደ ታች ውሰድ"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ተጠናቅቋል"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index aa4b3b704110..8fe0fb909197 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"الإعدادات"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"الدخول في وضع تقسيم الشاشة"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"القائمة"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"قائمة نافذة ضمن النافذة"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> يظهر في صورة داخل صورة"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"إذا كنت لا تريد أن يستخدم <xliff:g id="NAME">%s</xliff:g> هذه الميزة، فانقر لفتح الإعدادات، ثم أوقِف تفعيل هذه الميزة."</string>
<string name="pip_play" msgid="3496151081459417097">"تشغيل"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
+ <string name="divider_title" msgid="5482989479865361192">"أداة تقسيم الشاشة"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"تقسيم لليسار"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"تقسيم لليمين"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"تقسيم للأعلى"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسيم للأسفل"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"استخدام وضع \"التصفح بيد واحدة\""</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"بدء وضع \"التصفح بيد واحدة\""</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نقل إلى أسفل اليسار"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"إعدادات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"إغلاق فقاعة المحادثة"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"انقر لإعادة تشغيل هذا التطبيق والانتقال إلى وضع ملء الشاشة."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"انقر لإعادة تشغيل هذا التطبيق للحصول على عرض أفضل."</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_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"هل تريد إعادة تشغيل التطبيق لعرضه بشكل أفضل؟"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"يمكنك إعادة تشغيل التطبيق حتى يظهر بشكل أفضل على شاشتك، ولكن قد تفقد تقدمك أو أي تغييرات غير محفوظة."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"إلغاء"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
+ <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"رمز التطبيق"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
+ <string name="select_text" msgid="5139083974039906583">"اختيار"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"لقطة شاشة"</string>
+ <string name="close_text" msgid="4986518933445178928">"إغلاق"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
index a1ceda5fc987..82ab8e9ee15b 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"نافذة ضمن النافذة"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string>
- <string name="pip_close" msgid="9135220303720555525">"‏إغلاق PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"إغلاق"</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>
+ <string name="pip_move" msgid="158770205886688553">"نقل"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"توسيع"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"تصغير"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"انقر مرتين على "<annotation icon="home_icon">" الرئيسية "</annotation>" للوصول لعناصر التحكم."</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"قائمة نافذة ضمن النافذة"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"نقل لليسار"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"نقل لليمين"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"نقل للأعلى"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"نقل للأسفل"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"تمّ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 985d3b9b96fd..2aaf9245594d 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ছেটিং"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"বিভাজিত স্ক্ৰীন ম’ডলৈ যাওক"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"চিত্ৰৰ ভিতৰৰ চিত্ৰ মেনু"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> চিত্ৰৰ ভিতৰৰ চিত্ৰত আছে"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"আপুনি যদি <xliff:g id="NAME">%s</xliff:g> সুবিধাটো ব্যৱহাৰ কৰিব নোখোজে, তেন্তে ছেটিং খুলিবলৈ টিপক আৰু তালৈ গৈ ইয়াক অফ কৰক।"</string>
<string name="pip_play" msgid="3496151081459417097">"প্লে কৰক"</string>
@@ -33,19 +34,25 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
+ <string name="divider_title" msgid="5482989479865361192">"বিভাজিত স্ক্ৰীনৰ বিভাজক"</string>
<string name="accessibility_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_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_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_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_split_left" msgid="1713683765575562458">"বাওঁফালে বিভাজন কৰক"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"সোঁফালে বিভাজন কৰক"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"একেবাৰে ওপৰৰফালে বিভাজন কৰক"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"একেবাৰে তলৰফালে বিভাজন কৰক"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"তলৰ সোঁফালে নিয়ক"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ছেটিং"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল অগ্ৰাহ্য কৰক"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"বাৰ্তালাপ বাবল নকৰিব"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"নতুন বাৰ্তালাপ উপঙি থকা চিহ্নসমূহ অথবা bubbles হিচাপে প্ৰদর্শিত হয়। Bubbles খুলিবলৈ টিপক। এইটো স্থানান্তৰ কৰিবলৈ টানি নিয়ক।"</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"এপ্‌টো ৰিষ্টাৰ্ট কৰিবলৈ আৰু পূৰ্ণ স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ টিপক।"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"উন্নত ভিউৰ বাবে এপ্‌টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক।"</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_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"উন্নতভাৱে দেখা পাবলৈ ৰিষ্টাৰ্ট কৰিবনে?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"আপোনাৰ স্ক্ৰীনত উন্নতভাৱে দেখা পাবলৈ আপুনি এপ্‌টো ৰিষ্টাৰ্ট কৰিব পাৰে, কিন্তু আপুনি আপোনাৰ অগ্ৰগতি অথবা ছেভ নকৰা যিকোনো সালসলনি হেৰুৱাব পাৰে"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল কৰক"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
+ <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"এপৰ চিহ্ন"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
+ <string name="select_text" msgid="5139083974039906583">"বাছনি কৰক"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"স্ক্ৰীনশ্বট"</string>
+ <string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
index 8d7bd9f6a27e..34eaaea33609 100644
--- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"চিত্ৰৰ ভিতৰত চিত্ৰ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string>
- <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string>
+ <string name="pip_close" msgid="2955969519031223530">"বন্ধ কৰক"</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>
+ <string name="pip_move" msgid="158770205886688553">"স্থানান্তৰ কৰক"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"বিস্তাৰ কৰক"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"সংকোচন কৰক"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"নিয়ন্ত্ৰণৰ বাবে "<annotation icon="home_icon">"গৃহপৃষ্ঠা"</annotation>" বুটামত দুবাৰ টিপক"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"চিত্ৰৰ ভিতৰৰ চিত্ৰ মেনু।"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"বাওঁফাললৈ নিয়ক"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"সোঁফাললৈ নিয়ক"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ওপৰলৈ নিয়ক"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"তললৈ নিয়ক"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"হ’ল"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 8cd9b7a635ab..ad6e618c8a94 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana daxil olun"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Şəkildə Şəkil Menyusu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> şəkil içində şəkildədir"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Oxudun"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcısı"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Sol tam ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sol 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string>
@@ -46,6 +49,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="accessibility_split_left" msgid="1713683765575562458">"Sola ayırın"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Sağa ayırın"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Yuxarı ayırın"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Aşağı ayırın"</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 rejim başlasın"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Aşağıya sağa köçürün"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Yumrucuğu ləğv edin"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Söhbəti yumrucuqda göstərmə"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Yumrucuqlardan istifadə edərək söhbət edin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yeni söhbətlər üzən nişanlar və ya yumrucuqlar kimi görünür. Yumrucuğu açmaq üçün toxunun. Hərəkət etdirmək üçün sürüşdürün."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Qabarcıq"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"İdarə edin"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Qabarcıqdan imtina edilib."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Bu tətbiqi sıfırlayaraq tam ekrana keçmək üçün toxunun."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toxunaraq bu tətbiqi yenidən başladın ki, daha görüntü əldə edəsiniz."</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_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekrandan istifadə etmək üçün başqa tətbiqi sürüşdürüb gətirin"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Daha yaxşı görünüş üçün yenidən başladılsın?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Tətbiqi yenidən başlada bilərsiniz ki, ekranınızda daha yaxşı görünsün, lakin irəliləyişi və ya yadda saxlanmamış dəyişiklikləri itirə bilərsiniz"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Tətbiq ikonası"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seçin"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skrinşot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Bağlayın"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
index 87c46fa41a01..c45a09645075 100644
--- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Şəkil-içində-Şəkil"</string>
<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_close" msgid="2955969519031223530">"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>
+ <string name="pip_move" msgid="158770205886688553">"Köçürün"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Genişləndirin"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Yığcamlaşdırın"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Nizamlayıcılar üçün "<annotation icon="home_icon">"ƏSAS SƏHİFƏ "</annotation>" seçiminə iki dəfə basın"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Şəkildə şəkil menyusu."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sola köçürün"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sağa köçürün"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Yuxarı köçürün"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Aşağı köçürün"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hazırdır"</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 49524c608543..1dd09f5986ae 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Podešavanja"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Uđi na podeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni slike u slici."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je slika u slici"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Pusti"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Razdelnik podeljenog ekrana"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Režim celog ekrana za levi ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi ekran 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji ekran 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Režim celog ekrana za donji ekran"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Podelite levo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Podelite desno"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Podelite u vrhu"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podelite u dnu"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korišćenje režima jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokrenite režim jednom rukom"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premesti dole desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Podešavanja za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne koristi oblačiće za konverzaciju"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Ćaskajte u oblačićima"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nove konverzacije se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da biste otvorili oblačić. Prevucite da biste ga premestili."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste restartovali aplikaciju i prešli u režim celog ekrana."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza."</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_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Još"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
+ <string name="select_text" msgid="5139083974039906583">"Izaberite"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
</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 c87f30611a07..6dc4ab1cea79 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
@@ -19,10 +19,16 @@
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="2955969519031223530">"Zatvori"</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>
+ <string name="pip_move" msgid="158770205886688553">"Premesti"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Skupi"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">" HOME "</annotation>" za kontrole"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni Slika u slici."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomerite nalevo"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomerite nadesno"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomerite nagore"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomerite nadole"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 1767e0d66241..cda6e39180fe 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Налады"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Падзяліць экран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню рэжыму \"Відарыс у відарысе\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> з’яўляецца відарысам у відарысе"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Калі вы не хочаце, каб праграма <xliff:g id="NAME">%s</xliff:g> выкарыстоўвала гэту функцыю, дакраніцеся, каб адкрыць налады і адключыць яе."</string>
<string name="pip_play" msgid="3496151081459417097">"Прайграць"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Раздзяляльнік падзеленага экрана"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Падзяліць злева"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Падзяліць справа"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Падзяліць уверсе"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Падзяліць унізе"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Выкарыстоўваецца рэжым кіравання адной рукой"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запусціць рэжым кіравання адной рукой"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перамясціць правей і ніжэй"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налады \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Адхіліць апавяшчэнне"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Націсніце, каб перазапусціць гэту праграму і перайсці ў поўнаэкранны рэжым."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Націсніце, каб перазапусціць гэту праграму для лепшага прагляду."</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_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перазапусціць?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Вы можаце перазапусціць праграму, каб яна выглядала лепш на вашым экране, але пры гэтым могуць знікнуць даныя пра ваш прагрэс або незахаваныя змяненні"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасаваць"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Значок праграмы"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
+ <string name="select_text" msgid="5139083974039906583">"Выбраць"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Здымак экрана"</string>
+ <string name="close_text" msgid="4986518933445178928">"Закрыць"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index 3566bc372820..20e725f1aa09 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Відарыс у відарысе"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Закрыць"</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>
+ <string name="pip_move" msgid="158770205886688553">"Перамясціць"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Разгарнуць"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Згарнуць"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Двойчы націсніце "<annotation icon="home_icon">"ГАЛОЎНЫ"</annotation>" для пераходу ў налады"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню рэжыму \"Відарыс у відарысе\"."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Перамясціць улева"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Перамясціць управа"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Перамясціць уверх"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Перамясціць уніз"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Гатова"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index c22fb86a4d4d..b5d4eb195933 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Преминаване към разделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню за режима „Картина в картината“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е в режима „Картина в картината“"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ако не искате <xliff:g id="NAME">%s</xliff:g> да използва тази функция, докоснете, за да отворите настройките, и я изключете."</string>
<string name="pip_play" msgid="3496151081459417097">"Пускане"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Разделител в режима за разделен екран"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Разделяне в лявата част"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Разделяне в дясната част"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Разделяне в горната част"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Разделяне в долната част"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Използване на режима за работа с една ръка"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Стартиране на режима за работа с една ръка"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Преместване долу вдясно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Настройки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отхвърляне на балончетата"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Докоснете, за да рестартирате това приложение в режим на цял екран."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Докоснете, за да рестартирате това приложение с цел по-добър изглед."</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_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Да се рестартира ли с цел подобряване на изгледа?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартирате приложението, за да изглежда по-добре на екрана. Възможно е обаче да загубите напредъка си или незапазените промени"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отказ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Икона на приложението"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Още"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
+ <string name="select_text" msgid="5139083974039906583">"Избиране"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Екранна снимка"</string>
+ <string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
index 91049fd2cf02..e9906f952455 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картина в картината"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Затваряне"</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>
+ <string name="pip_move" msgid="158770205886688553">"Преместване"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Разгъване"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Свиване"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"За достъп до контролите натиснете два пъти "<annotation icon="home_icon">"НАЧАЛО"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню за функцията „Картина в картината“."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Преместване наляво"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Преместване надясно"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Преместване нагоре"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Преместване надолу"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index c0944e0584e6..5c95f9e5ff23 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"সেটিংস"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"\'স্প্লিট স্ক্রিন\' মোড চালু করুন"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"মেনু"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ছবির-মধ্যে-ছবি মেনু"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"ছবির-মধ্যে-ছবি তে <xliff:g id="NAME">%s</xliff:g> আছেন"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> কে এই বৈশিষ্ট্যটি ব্যবহার করতে দিতে না চাইলে ট্যাপ করে সেটিংসে গিয়ে সেটি বন্ধ করে দিন।"</string>
<string name="pip_play" msgid="3496151081459417097">"চালান"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
+ <string name="divider_title" msgid="5482989479865361192">"স্প্লিট স্ক্রিন বিভাজক"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"স্ক্রিনের বাঁদিকে স্প্লিট করুন"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"স্ক্রিনের ডানদিকে স্প্লিট করুন"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"স্ক্রিনের উপরের দিকে স্প্লিট করুন"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"স্প্লিট করার বোতাম"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপ আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"\'এক হাতে ব্যবহার করার মোড\' শুরু করুন"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"নিচে ডান দিকে সরান"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> সেটিংস"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল খারিজ করুন"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন ও \'ফুল-স্ক্রিন\' মোড ব্যবহার করুন।"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন।"</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_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"আরও ভালভাবে দেখার জন্য রিস্টার্ট করবেন?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"স্ক্রিনে আরও ভালভাবে দেখার জন্য আপনি অ্যাপ রিস্টার্ট করতে পারবেন, এর ফলে চলতে থাকা কোনও প্রক্রিয়া বা সেভ না করা পরিবর্তন হারিয়ে যেতে পারে"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল করুন"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
+ <string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"অ্যাপ আইকন"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
+ <string name="select_text" msgid="5139083974039906583">"বেছে নিন"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"স্ক্রিনশট"</string>
+ <string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
index 792708d128a5..b515154ec3e0 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ছবির-মধ্যে-ছবি"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string>
+ <string name="pip_close" msgid="2955969519031223530">"বন্ধ করুন"</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>
+ <string name="pip_move" msgid="158770205886688553">"সরান"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"বড় করুন"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"আড়াল করুন"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"কন্ট্রোলের জন্য "<annotation icon="home_icon">" হোম "</annotation>" বোতামে ডবল প্রেস করুন"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ছবির-মধ্যে-ছবি মেনু।"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"বাঁদিকে সরান"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ডানদিকে সরান"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"উপরে তুলুন"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"নিচে নামান"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"হয়ে গেছে"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index ae01c641cc43..f9ff29cd8a41 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvori podijeljeni ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni načina rada slike u slici"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je u načinu priakza Slika u slici"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproduciraj"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik ekrana"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog ekrana"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevo cijeli ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevo 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gore 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji ekran kao cijeli ekran"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Podjela ulijevo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Podjela udesno"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Podjela nagore"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podjela nadolje"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Započinjanje načina rada jednom rukom"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pomjerite dolje desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke aplikacije <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nemoj prikazivati razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatajte koristeći oblačiće"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da otvorite oblačić. Prevucite da ga premjestite."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da ponovo pokrenete ovu aplikaciju i aktivirate prikaz preko cijelog ekrana."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza."</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_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Ponovo pokrenuti za bolji prikaz?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovo pokrenuti aplikaciju da bolje izgleda na ekranu, ali možete izgubiti napredak ili izmjene koje nisu sačuvane"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
+ <string name="select_text" msgid="5139083974039906583">"Odabir"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
index b7f0dca1b5a5..99e076b31180 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
@@ -19,10 +19,16 @@
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 sliku u slici"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Zatvori"</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>
+ <string name="pip_move" msgid="158770205886688553">"Premjesti"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Suzi"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">"POČETNI EKRAN"</annotation>" za kontrole"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni za način rada slika u slici."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomjeranje ulijevo"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomjeranje udesno"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomjeranje nagore"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomjeranje nadolje"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 8a522b3e6397..b54e60d1d11a 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -22,7 +22,8 @@
<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_menu_accessibility_title" msgid="8129016817688656249">"Menú d\'imatge sobre imatge"</string>
+ <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en mode d\'imatge sobre imatge"</string>
<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>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Separador de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla esquerra completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pantalla esquerra al 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string>
@@ -46,12 +49,16 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Pantalla superior al 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Divideix a l\'esquerra"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Divideix a la dreta"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Divideix a la part superior"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divideix a la part inferior"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"S\'està utilitzant el mode d\'una mà"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Inicia el mode d\'una mà"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Surt del mode d\'una mà"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configuració de les bombolles: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú addicional"</string>
+ <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú de desbordament"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Torna a afegir a la pila"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>) i <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> més"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mou a baix a la dreta"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuració de l\'aplicació <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora la bombolla"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostris la conversa com a bombolla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xateja amb bombolles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les converses noves es mostren com a icones flotants o bombolles. Toca per obrir una bombolla. Arrossega-la per moure-la."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bombolla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestiona"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"La bombolla s\'ha ignorat."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toca per reiniciar aquesta aplicació i passar a pantalla completa."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització."</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_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vols reiniciar per a una millor visualització?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Pots reiniciar l\'aplicació perquè es vegi millor en pantalla, però és possible que perdis el teu progrés o qualsevol canvi que no hagis desat"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel·la"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icona de l\'aplicació"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Més"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selecciona"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+ <string name="close_text" msgid="4986518933445178928">"Tanca"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index 1c560c7afa06..d51a78b8e92d 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -17,12 +17,18 @@
<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">"Pantalla en pantalla"</string>
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Imatge sobre imatge"</string>
<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_close" msgid="2955969519031223530">"Tanca"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mou"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Desplega"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Replega"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Prem dos cops "<annotation icon="home_icon">"INICI"</annotation>" per accedir als controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú d\'imatge sobre imatge."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mou cap a l\'esquerra"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mou cap a la dreta"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mou cap amunt"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mou cap avall"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fet"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index d0cf80aef38c..d6e4f9f973b5 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavení"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivovat rozdělenou obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Nabídka"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Nabídka režimu obrazu v obraze"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikace <xliff:g id="NAME">%s</xliff:g> je v režimu obraz v obraze"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Přehrát"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Čára rozdělující obrazovku"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levá část na celou obrazovku"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % vlevo"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % nahoře"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolní část na celou obrazovku"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdělit vlevo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdělit vpravo"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdělit nahoře"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdělit dole"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používání režimu jedné ruky"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustit režim jedné ruky"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Přesunout vpravo dolů"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavení <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavřít bublinu"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovat konverzaci v bublinách"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatujte pomocí bublin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nové konverzace se zobrazují jako plovoucí ikony, neboli bubliny. Klepnutím bublinu otevřete. Přetažením ji posunete."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Klepnutím aplikaci restartujete a přejdete na režim celé obrazovky"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</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_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restartovat pro lepší zobrazení?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikaci můžete restartovat, aby na obrazovce vypadala lépe, ale můžete přijít o svůj postup nebo o neuložené změny"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušit"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikace"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Více"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
+ <string name="select_text" msgid="5139083974039906583">"Vybrat"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Snímek obrazovky"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
index 9a8cc2b4d70e..72e1ae907cfb 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz v obraze"</string>
<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_close" msgid="2955969519031223530">"Zavřít"</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>
+ <string name="pip_move" msgid="158770205886688553">"Přesunout"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Rozbalit"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Sbalit"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Ovládací prvky zobrazíte dvojitým stisknutím "<annotation icon="home_icon">"HOME"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Nabídka režimu obrazu v obraze"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Přesunout doleva"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Přesunout doprava"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Přesunout nahoru"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Přesunout dolů"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hotovo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index bb81c10c6e1b..da8723f8742d 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Indstillinger"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Åbn opdelt skærm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu for integreret billede"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> vises som integreret billede"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Afspil"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Adskiller til opdelt skærm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vis venstre del i fuld skærm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Venstre 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Øverste 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vis nederste del i fuld skærm"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Vis i venstre side"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Vis i højre side"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Vis øverst"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Vis nederst"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Brug af enhåndstilstand"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndstilstand"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flyt ned til højre"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat ved hjælp af bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen blev lukket."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tryk for at genstarte denne app, og gå til fuld skærm."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tryk for at genstarte denne app, så visningen forbedres."</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_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Træk en anden app hertil for at bruge opdelt skærm"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vil du genstarte for at få en bedre visning?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan genstarte appen, så den ser bedre ud på din skærm, men du mister muligvis dine fremskridt og de ændringer, der ikke gemt"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuller"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
+ <string name="select_text" msgid="5139083974039906583">"Vælg"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Luk"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
index cba660ac723c..5881b0674ad2 100644
--- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Integreret billede"</string>
<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_close" msgid="2955969519031223530">"Luk"</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>
+ <string name="pip_move" msgid="158770205886688553">"Flyt"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Udvid"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Skjul"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Tryk to gange på "<annotation icon="home_icon">"HJEM"</annotation>" for at se indstillinger"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu for integreret billede."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flyt til venstre"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flyt til højre"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flyt op"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flyt ned"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Udfør"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index c5d945a982ef..11090e02a927 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"„Geteilter Bildschirm“ aktivieren"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü „Bild im Bild“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Wiedergeben"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Bildschirmteiler"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vollbild links"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % links"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % oben"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vollbild unten"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Links teilen"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Rechts teilen"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Oben teilen"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Unten teilen"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Einhandmodus wird verwendet"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Einhandmodus starten"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Einstellungen für <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Unterhaltung nicht als Bubble anzeigen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles zum Chatten verwenden"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Neue Unterhaltungen erscheinen als unverankerte Symbole, „Bubbles“ genannt. Wenn du eine Bubble öffnen möchtest, tippe sie an. Wenn du sie verschieben möchtest, zieh an ihr."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Verwalten"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble verworfen."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tippe, um die App im Vollbildmodus neu zu starten."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tippe, um diese App neu zu starten und die Ansicht zu verbessern."</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_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Für bessere Darstellung neu starten?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kannst die App neu starten, damit sie an die Bildschirmabmessungen deines Geräts angepasst dargestellt wird – jedoch können dadurch dein Fortschritt oder nicht gespeicherte Änderungen verloren gehen."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Abbrechen"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
+ <string name="select_text" msgid="5139083974039906583">"Auswählen"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Schließen"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 02a1b66eb63f..5c5cbda296a1 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild im Bild"</string>
<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_close" msgid="2955969519031223530">"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>
+ <string name="pip_move" msgid="158770205886688553">"Bewegen"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Maximieren"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Minimieren"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Für Steuerelemente 2× "<annotation icon="home_icon">"STARTBILDSCHIRMTASTE"</annotation>" drücken"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menü „Bild im Bild“."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Nach links bewegen"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Nach rechts bewegen"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Nach oben bewegen"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Nach unten bewegen"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fertig"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 70f55058925c..1eca52ae7701 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ρυθμίσεις"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Μετάβαση σε διαχωρισμό οθόνης"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Μενού"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Μενού λειτουργίας Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Η λειτουργία picture-in-picture είναι ενεργή σε <xliff:g id="NAME">%s</xliff:g>."</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Εάν δεν θέλετε να χρησιμοποιείται αυτή η λειτουργία από την εφαρμογή <xliff:g id="NAME">%s</xliff:g>, πατήστε για να ανοίξετε τις ρυθμίσεις και απενεργοποιήστε την."</string>
<string name="pip_play" msgid="3496151081459417097">"Αναπαραγωγή"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Διαχωριστικό οθόνης"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Διαχωρισμός αριστερά"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Διαχωρισμός δεξιά"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Διαχωρισμός επάνω"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Διαχωρισμός κάτω"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Χρήση λειτουργίας ενός χεριού"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Έναρξη λειτουργίας ενός χεριού"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Μετακίνηση κάτω δεξιά"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ρυθμίσεις <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Παράβλ. για συννεφ."</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Πατήστε για επανεκκίνηση αυτής της εφαρμογής και ενεργοποίηση πλήρους οθόνης."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή."</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_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Επανεκκίνηση για καλύτερη προβολή;"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Μπορείτε να επανεκκινήσετε την εφαρμογή για να προβάλλεται καλύτερα στην οθόνη σας, αλλά η πρόοδός σας και τυχόν μη αποθηκευμένες αλλαγές ενδέχεται να χαθούν."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ακύρωση"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Εικονίδιο εφαρμογής"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
+ <string name="select_text" msgid="5139083974039906583">"Επιλογή"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Στιγμιότυπο οθόνης"</string>
+ <string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
index 24cd030cd754..a80e2c72de7e 100644
--- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Κλείσιμο"</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>
+ <string name="pip_move" msgid="158770205886688553">"Μετακίνηση"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Ανάπτυξη"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Σύμπτυξη"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Πατ. δύο φορές το κουμπί "<annotation icon="home_icon">"αρχ. οθ."</annotation>" για στ. ελέγχου"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Μενού λειτουργίας Picture-in-Picture."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Μετακίνηση αριστερά"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Μετακίνηση δεξιά"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Μετακίνηση επάνω"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Μετακίνηση κάτω"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Τέλος"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 0b5aefa5c72e..8493434f87af 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</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_dialog_title" msgid="7739895354143295358">"See and do more"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+ <string name="select_text" msgid="5139083974039906583">"Select"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Close"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
</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 82257b42814d..71d02271090d 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Close"</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>
+ <string name="pip_move" msgid="158770205886688553">"Move"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 0b5aefa5c72e..e9c78fc26f07 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-Picture Menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
@@ -61,24 +68,46 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles anytime"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tap Manage to turn off bubbles from this app"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</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_dialog_title" msgid="7739895354143295358">"See and do more"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App Icon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+ <string name="select_text" msgid="5139083974039906583">"Select"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Close"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
</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 82257b42814d..09def6b69f06 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -17,12 +17,18 @@
<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">"Picture-in-picture"</string>
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Close"</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>
+ <string name="pip_move" msgid="158770205886688553">"Move"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Double press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 0b5aefa5c72e..8493434f87af 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</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_dialog_title" msgid="7739895354143295358">"See and do more"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+ <string name="select_text" msgid="5139083974039906583">"Select"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Close"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
</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 82257b42814d..71d02271090d 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Close"</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>
+ <string name="pip_move" msgid="158770205886688553">"Move"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 0b5aefa5c72e..8493434f87af 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Settings"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Enter split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Picture-in-picture menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Play"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Manage"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubble dismissed."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tap to restart this app for a better view."</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_dialog_title" msgid="7739895354143295358">"See and do more"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"More"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+ <string name="select_text" msgid="5139083974039906583">"Select"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Close"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
</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 82257b42814d..71d02271090d 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Close"</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>
+ <string name="pip_move" msgid="158770205886688553">"Move"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Double-press "<annotation icon="home_icon">"HOME"</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Move down"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Done"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 5c3d0f65374a..466a2bf885ba 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎Settings‎‏‎‎‏‎"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎Enter split screen‎‏‎‎‏‎"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎Menu‎‏‎‎‏‎"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎Picture-in-Picture Menu‎‏‎‎‏‎"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ is in picture-in-picture‎‏‎‎‏‎"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎Play‎‏‎‎‏‎"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎App may not work with split-screen.‎‏‎‎‏‎"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎App does not support split-screen.‎‏‎‎‏‎"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎This app can only be opened in 1 window.‎‏‎‎‏‎"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎App may not work on a secondary display.‎‏‎‎‏‎"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎App does not support launch on secondary displays.‎‏‎‎‏‎"</string>
<string name="accessibility_divider" msgid="703810061635792791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Split-screen divider‎‏‎‎‏‎"</string>
+ <string name="divider_title" msgid="5482989479865361192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎Split-screen divider‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎Left full screen‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎Left 70%‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎Left 50%‎‏‎‎‏‎"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎Top 50%‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎Top 30%‎‏‎‎‏‎"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎Bottom full screen‎‏‎‎‏‎"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎Split left‎‏‎‎‏‎"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎Split right‎‏‎‎‏‎"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎Split top‎‏‎‎‏‎"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎Split bottom‎‏‎‎‏‎"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‎Using one-handed mode‎‏‎‎‏‎"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎To exit, swipe up from the bottom of the screen or tap anywhere above the app‎‏‎‎‏‎"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎Start one-handed mode‎‏‎‎‏‎"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎Move bottom right‎‏‎‎‏‎"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ settings‎‏‎‎‏‎"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎Dismiss bubble‎‏‎‎‏‎"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎Don’t bubble conversation‎‏‎‎‏‎"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎Chat using bubbles‎‏‎‎‏‎"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it.‎‏‎‎‏‎"</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎‎Bubble‎‏‎‎‏‎"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎Manage‎‏‎‎‏‎"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎Bubble dismissed.‎‏‎‎‏‎"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎Tap to restart this app and go full screen.‎‏‎‎‏‎"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎Tap to restart this app for a better view.‎‏‎‎‏‎"</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_dialog_title" msgid="7739895354143295358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎See and do more‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎Drag in another app for split-screen‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎Double-tap outside an app to reposition it‎‏‎‎‏‎"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎Restart for a better view?‎‏‎‎‏‎"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes‎‏‎‎‏‎"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎Cancel‎‏‎‎‏‎"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎Restart‎‏‎‎‏‎"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎Don’t show again‎‏‎‎‏‎"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
+ <string name="handle_text" msgid="1766582106752184456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎Handle‎‏‎‎‏‎"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎App Icon‎‏‎‎‏‎"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎Fullscreen‎‏‎‎‏‎"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎Desktop Mode‎‏‎‎‏‎"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎Split Screen‎‏‎‎‏‎"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎More‎‏‎‎‏‎"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎Float‎‏‎‎‏‎"</string>
+ <string name="select_text" msgid="5139083974039906583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎Select‎‏‎‎‏‎"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎Screenshot‎‏‎‎‏‎"</string>
+ <string name="close_text" msgid="4986518933445178928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎Close‎‏‎‎‏‎"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎Close Menu‎‏‎‎‏‎"</string>
</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 a6e494cfed3c..405770166274 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎Picture-in-Picture‎‏‎‎‏‎"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎(No title program)‎‏‎‎‏‎"</string>
- <string name="pip_close" msgid="9135220303720555525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎Close PIP‎‏‎‎‏‎"</string>
+ <string name="pip_close" msgid="2955969519031223530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎Close‎‏‎‎‏‎"</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>
+ <string name="pip_move" msgid="158770205886688553">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎Move‎‏‎‎‏‎"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎Expand‎‏‎‎‏‎"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‏‎‎Collapse‎‏‎‎‏‎"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎Double press ‎‏‎‎‏‏‎"<annotation icon="home_icon">"‎‏‎‎‏‏‏‎HOME‎‏‎‎‏‏‎"</annotation>"‎‏‎‎‏‏‏‎ for controls‎‏‎‎‏‎"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‎Picture-in-Picture menu.‎‏‎‎‏‎"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎Move left‎‏‎‎‏‎"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎Move right‎‏‎‎‏‎"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎Move up‎‏‎‎‏‎"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎Move down‎‏‎‎‏‎"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎Done‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index e523ae53b0cc..b0175913a442 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla en pantalla"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en modo de Pantalla en pantalla"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproducir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir a la izquierda"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir a la derecha"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cómo usar el modo de una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o presiona cualquier parte arriba de la app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar el modo de una mano"</string>
@@ -61,6 +68,8 @@
<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>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Cuadro"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Se descartó el cuadro."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Presiona para reiniciar esta app y acceder al modo de pantalla completa."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Presiona para reiniciar esta app y tener una mejor vista."</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_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra app para el modo de pantalla dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"¿Quieres reiniciar para que se vea mejor?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puedes reiniciar la app para que se vea mejor en la pantalla, pero podrías perder tu progreso o cualquier cambio que no hayas guardado"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ícono de la app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+ <string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
</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 458f6b15b857..e0f3297ff966 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla en pantalla"</string>
<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_close" msgid="2955969519031223530">"Cerrar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expandir"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Presiona dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla en pantalla"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover hacia la derecha"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover hacia arriba"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover hacia abajo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Listo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 974960708190..986a2c856cd1 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ajustes"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Introducir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de imagen en imagen"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está en imagen en imagen"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproducir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string>
@@ -46,10 +49,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar Modo una mano"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir en la parte izquierda"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir en la parte derecha"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar Modo una mano"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del Modo una mano"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo Una mano"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del modo Una mano"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Ajustes de las burbujas de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú adicional"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Volver a añadir a la pila"</string>
@@ -61,9 +68,11 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abajo a la derecha"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ajustes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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 una burbuja para abrirla. Arrástrala para moverla."</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamados \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string>
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toca para reiniciar esta aplicación e ir a la pantalla completa."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</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_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra aplicación para activar la pantalla dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"¿Reiniciar para que se vea mejor?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puedes reiniciar la aplicación para que se vea mejor en la pantalla, pero puedes perder tu progreso o cualquier cambio que no hayas guardado"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icono de la aplicación"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+ <string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 0a690984dac5..38be3effe356 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Imagen en imagen"</string>
<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_close" msgid="2955969519031223530">"Cerrar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Mostrar"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de imagen en imagen."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover hacia la derecha"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover hacia arriba"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover hacia abajo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hecho"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index a5f82a6452c4..b84c4f2ae8cb 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Seaded"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Ava jagatud ekraanikuva"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menüü"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menüü Pilt pildis"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on režiimis Pilt pildis"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Esita"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Ekraanijagaja"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasak täisekraan"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasak: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ülemine: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alumine täisekraan"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Jaga vasakule"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Jaga paremale"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Jaga üles"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Jaga alla"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ühekäerežiimi kasutamine"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Väljumiseks pühkige ekraani alaosast üles või puudutage rakenduse kohal olevat ala"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ühekäerežiimi käivitamine"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Teisalda alla paremale"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Rakenduse <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> seaded"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Sule mull"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ära kuva vestlust mullina"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Vestelge mullide abil"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Uued vestlused kuvatakse hõljuvate ikoonidena ehk mullidena. Puudutage mulli avamiseks. Lohistage mulli, et seda liigutada."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Mull"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Halda"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Mullist loobuti."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Puudutage rakenduse taaskäivitamiseks ja täisekraanrežiimi aktiveerimiseks."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada."</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_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskäivitada parema vaate saavutamiseks?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Rakenduse ikoon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
+ <string name="select_text" msgid="5139083974039906583">"Vali"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Ekraanipilt"</string>
+ <string name="close_text" msgid="4986518933445178928">"Sule"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
index dc0232303a70..a93cee51ce07 100644
--- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pilt pildis"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Sule"</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>
+ <string name="pip_move" msgid="158770205886688553">"Teisalda"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Laienda"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Ahenda"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Nuppude nägemiseks vajutage 2 korda nuppu "<annotation icon="home_icon">"AVAKUVA"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menüü Pilt pildis."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Teisalda vasakule"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Teisalda paremale"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Teisalda üles"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Teisalda alla"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Valmis"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index caa335a96222..a2972193d5a3 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -22,8 +22,9 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ezarpenak"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Sartu pantaila zatituan"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menua"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pantaila txiki gainjarriaren menua"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Pantaila txiki gainjarrian dago <xliff:g id="NAME">%s</xliff:g>"</string>
- <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>
+ <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> zerbitzuak eginbide hori erabiltzea nahi ez baduzu, sakatu hau ezarpenak ireki eta aukera desaktibatzeko."</string>
<string name="pip_play" msgid="3496151081459417097">"Erreproduzitu"</string>
<string name="pip_pause" msgid="690688849510295232">"Pausatu"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Joan hurrengora"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Pantaila-zatitzailea"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ezarri ezkerraldea % 70en"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ezarri goialdea % 30en"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ezarri behealdea pantaila osoan"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Zatitu ezkerraldean"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Zatitu eskuinaldean"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Zatitu goialdean"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Zatitu behealdean"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Esku bakarreko modua erabiltzea"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Irteteko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Abiarazi esku bakarreko modua"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Eraman behealdera, eskuinetara"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aplikazioaren ezarpenak"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baztertu burbuila"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ez erakutsi elkarrizketak burbuila gisa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Txateatu burbuilen bidez"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Elkarrizketa berriak ikono gainerakor edo burbuila gisa agertzen dira. Sakatu burbuila irekitzeko. Arrasta ezazu mugitzeko."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbuila"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kudeatu"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Baztertu da globoa."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Saka ezazu aplikazioa berrabiarazteko, eta ezarri pantaila osoko modua."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Hobeto ikusteko, sakatu hau aplikazioa berrabiarazteko."</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_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Pantaila zatituta ikusteko, arrastatu beste aplikazio bat"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Aplikazioa berrabiarazi nahi duzu itxura hobea izan dezan?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikazioa berrabiarazi egin dezakezu itxura hobea izan dezan, baina agian garapena edo gorde gabeko aldaketak galduko dituzu"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
+ <string name="select_text" msgid="5139083974039906583">"Hautatu"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Pantaila-argazkia"</string>
+ <string name="close_text" msgid="4986518933445178928">"Itxi"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
index bce06da2c66f..4b752fc9d1c4 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantaila txiki gainjarria"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Itxi"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mugitu"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Zabaldu"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Tolestu"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Kontrolatzeko aukerak atzitzeko, sakatu birritan "<annotation icon="home_icon">"HASIERA"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Pantaila txiki gainjarriaren menua."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Eraman ezkerrera"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Eraman eskuinera"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Eraman gora"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Eraman behera"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Eginda"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 761fb9ddeb2f..b379428861e3 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"تنظیمات"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ورود به حالت «صفحهٔ دونیمه»"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"منو"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"منو تصویر در تصویر"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> درحالت تصویر در تصویر است"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"اگر نمی‌خواهید <xliff:g id="NAME">%s</xliff:g> از این قابلیت استفاده کند، با ضربه زدن، تنظیمات را باز کنید و آن را خاموش کنید."</string>
<string name="pip_play" msgid="3496151081459417097">"پخش"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره می‌تواند باز شود."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راه‌اندازی در نمایشگرهای ثانویه پشتیبانی نمی‌کند."</string>
<string name="accessibility_divider" msgid="703810061635792791">"تقسیم‌کننده صفحه"</string>
+ <string name="divider_title" msgid="5482989479865361192">"تقسیم‌کننده صفحهٔ دونیمه"</string>
<string name="accessibility_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>
@@ -46,12 +49,16 @@
<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_split_left" msgid="1713683765575562458">"تقسیم از چپ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"تقسیم از راست"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"تقسیم از بالا"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسیم از پایین"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یک‌دستی"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحه‌نمایش تند به‌طرف بالا بکشید یا در هر جایی از بالای برنامه که می‌خواهید ضربه بزنید"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یک‌دستی»"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یک‌دستی»"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابک‌های <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"لبریزشده"</string>
+ <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"انتقال به پایین سمت چپ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"تنظیمات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"رد کردن حبابک"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"برای بازراه‌اندازی این برنامه و تغییر به حالت تمام‌صفحه، ضربه بزنید."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراه‌اندازی شود."</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_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراه‌اندازی شود؟"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"می‌توانید برنامه را بازراه‌اندازی کنید تا بهتر روی صفحه‌نمایش نشان داده شود، اما ممکن است پیشرفت یا تغییرات ذخیره‌نشده را ازدست بدهید"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراه‌اندازی"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
+ <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"نماد برنامه"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"تمام‌صفحه"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
+ <string name="select_text" msgid="5139083974039906583">"انتخاب"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"نماگرفت"</string>
+ <string name="close_text" msgid="4986518933445178928">"بستن"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index ff9a03c6cefb..55394cbdc31a 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر در تصویر"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string>
- <string name="pip_close" msgid="9135220303720555525">"‏بستن PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"بستن"</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>
+ <string name="pip_move" msgid="158770205886688553">"انتقال"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"گسترده کردن"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"جمع کردن"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"برای کنترل‌ها، دکمه "<annotation icon="home_icon">"صفحه اصلی"</annotation>" را دوبار فشار دهید"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"منوی تصویر در تصویر."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"انتقال به‌چپ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"انتقال به‌راست"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"انتقال به‌بالا"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"انتقال به‌پایین"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"تمام"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index c809b4879e71..14b35566c938 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Asetukset"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Avaa jaettu näyttö"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valikko"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kuva kuvassa ‑valikko"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> on kuva kuvassa ‑tilassa"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Toista"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Näytönjakaja"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasen koko näytölle"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasen 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yläosa 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alaosa koko näytölle"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Vasemmalla"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Oikealla"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Ylhäällä"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alhaalla"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Yhden käden moodin käyttö"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Käynnistä yhden käden moodi"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Siirrä oikeaan alareunaan"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: asetukset"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ohita kupla"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Älä näytä kuplia keskusteluista"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chattaile kuplien avulla"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Uudet keskustelut näkyvät kelluvina kuvakkeina tai kuplina. Avaa kupla napauttamalla. Siirrä sitä vetämällä."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Kupla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Ylläpidä"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Kupla ohitettu."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Napauta, niin sovellus käynnistyy uudelleen ja siirtyy koko näytön tilaan."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana."</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_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Käynnistetäänkö sovellus uudelleen, niin saat paremman näkymän?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Voit käynnistää sovelluksen uudelleen, jotta se näyttää paremmalta näytöllä, mutta saatat menettää edistymisesi tai tallentamattomat muutokset"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Peru"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Sovelluskuvake"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
+ <string name="select_text" msgid="5139083974039906583">"Valitse"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Kuvakaappaus"</string>
+ <string name="close_text" msgid="4986518933445178928">"Sulje"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
index 3e8bf9032780..f580d01691f9 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Kuva kuvassa"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Sulje"</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>
+ <string name="pip_move" msgid="158770205886688553">"Siirrä"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Laajenna"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Tiivistä"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Asetukset: paina "<annotation icon="home_icon">"ALOITUSNÄYTTÖPAINIKETTA"</annotation>" kahdesti"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Kuva kuvassa ‑valikko."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Siirrä vasemmalle"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Siirrä oikealle"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Siirrä ylös"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Siirrä alas"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Valmis"</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 62b2bb65a603..df3e345310f0 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Entrer dans l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu d\'incrustation d\'image"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode d\'incrustation d\'image"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Lire"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % à la gauche"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % dans le haut"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Plein écran dans le bas"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Diviser à gauche"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Diviser à droite"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Diviser dans la partie supérieure"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Diviser dans la partie inférieure"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Touchez pour redémarrer cette application et passer en plein écran."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</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_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre application pour utiliser l\'écran partagé"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour un meilleur affichage?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
+ <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
+ <string name="close_text" msgid="4986518933445178928">"Fermer"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
</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 66e13b89c64b..39a785d4fcc0 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Incrustation d\'image"</string>
<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_close" msgid="2955969519031223530">"Fermer"</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>
+ <string name="pip_move" msgid="158770205886688553">"Déplacer"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Développer"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Réduire"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation>" pour les commandes"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu d\'incrustation d\'image."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Déplacer vers la gauche"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Déplacer vers la droite"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Déplacer vers le haut"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Déplacer vers le bas"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index b3e22af0a3e3..7e3eae0fb41a 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Paramètres"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Accéder à l\'écran partagé"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu \"Picture-in-picture\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> est en mode Picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Lecture"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Écran de gauche en plein écran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Écran de gauche à 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Écran du haut à 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Écran du bas en plein écran"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Affichée à gauche"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Affichée à droite"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Affichée en haut"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Affichée en haut"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string>
@@ -61,9 +68,11 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou de bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle 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="restart_button_description" msgid="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</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_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour en améliorer son aspect sur votre écran, mais vous risquez de perdre votre progression ou les modifications non enregistrées"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icône d\'application"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
+ <string name="close_text" msgid="4986518933445178928">"Fermer"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
index ed9baf5b6215..db4bc54cf665 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<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_close" msgid="2955969519031223530">"Fermer"</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>
+ <string name="pip_move" msgid="158770205886688553">"Déplacer"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Développer"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Réduire"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Commandes : appuyez deux fois sur "<annotation icon="home_icon">"ACCUEIL"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu \"Picture-in-picture\"."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Déplacer vers la gauche"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Déplacer vers la droite"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Déplacer vers le haut"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Déplacer vers le bas"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"OK"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index b8e039602243..a63930d2f10b 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuración"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Inserir pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menú de pantalla superposta"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está na pantalla superposta"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproducir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla completa á esquerda"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % á esquerda"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % arriba"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla completa abaixo"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir (esquerda)"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir (dereita)"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir (arriba)"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir (abaixo)"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como se usa o modo dunha soa man?"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo dunha soa man"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover á parte inferior dereita"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar burbulla"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mostrar a conversa como burbulla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatear usando burbullas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"As conversas novas aparecen como iconas flotantes ou burbullas. Toca para abrir a burbulla e arrastra para movela."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Xestionar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ignorouse a burbulla."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toca o botón para reiniciar esta aplicación e abrila en pantalla completa."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización."</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_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra outra aplicación para usar a pantalla dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Queres reiniciar a aplicación para que se vexa mellor?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Podes reiniciar a aplicación para que se vexa mellor na pantalla, pero podes perder o progreso que levas feito ou calquera cambio que non gardases"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icona de aplicación"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+ <string name="close_text" msgid="4986518933445178928">"Pechar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
index a057434d7853..22e68d3707ac 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla superposta"</string>
<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_close" msgid="2955969519031223530">"Pechar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Despregar"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Preme "<annotation icon="home_icon">"INICIO"</annotation>" dúas veces para acceder aos controis"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla superposta."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover cara á esquerda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover cara á dereita"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover cara arriba"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover cara abaixo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Feito"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index deda2d755e20..f3a31620f696 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"સેટિંગ"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"વિભાજિત સ્ક્રીન મોડમાં દાખલ થાઓ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"મેનૂ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ચિત્રમાં ચિત્ર મેનૂ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ચિત્રમાં-ચિત્રની અંદર છે"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"જો તમે નથી ઇચ્છતા કે <xliff:g id="NAME">%s</xliff:g> આ સુવિધાનો ઉપયોગ કરે, તો સેટિંગ ખોલવા માટે ટૅપ કરો અને તેને બંધ કરો."</string>
<string name="pip_play" msgid="3496151081459417097">"ચલાવો"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
+ <string name="divider_title" msgid="5482989479865361192">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ડાબે વિભાજિત કરો"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"જમણે વિભાજિત કરો"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ઉપર વિભાજિત કરો"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"નીચે વિભાજિત કરો"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"એક-હાથે વાપરો મોડ શરૂ કરો"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"નીચે જમણે ખસેડો"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> સેટિંગ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોડી દો"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"આ ઍપ ફરીથી ચાલુ કરવા માટે ટૅપ કરીને પૂર્ણ સ્ક્રીન કરો."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો."</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_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"બહેતર વ્યૂ માટે ફરીથી શરૂ કરીએ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"તમે ઍપને ફરીથી શરૂ કરી શકો છો, જેથી તે તમારી સ્ક્રીન પર વધુ સારી રીતે દેખાય, પરંતુ આમ કરવાથી તમે તમારી ઍપ પર કરી હોય એવી કોઈ પ્રક્રિયાની પ્રગતિ અથવા સાચવ્યા ન હોય એવો કોઈપણ ફેરફાર ગુમાવી શકો છો"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"રદ કરો"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ફરી શરૂ કરો"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ફરીથી બતાવશો નહીં"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ઍપનું આઇકન"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
+ <string name="select_text" msgid="5139083974039906583">"પસંદ કરો"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"સ્ક્રીનશૉટ"</string>
+ <string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
index d9525910e4c6..01b9b4b987d0 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ચિત્રમાં-ચિત્ર"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string>
+ <string name="pip_close" msgid="2955969519031223530">"બંધ કરો"</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>
+ <string name="pip_move" msgid="158770205886688553">"ખસેડો"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"મોટું કરો"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"નાનું કરો"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"નિયંત્રણો માટે "<annotation icon="home_icon">"હોમ"</annotation>" બટન બે વાર દબાવો"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ચિત્રમાં ચિત્ર મેનૂ."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ડાબે ખસેડો"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"જમણે ખસેડો"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ઉપર ખસેડો"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"નીચે ખસેડો"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"થઈ ગયું"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 36b11514c7e5..134fb9712098 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन मोड में जाएं"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेन्यू"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"पिक्चर में पिक्चर मेन्यू"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"पिक्चर में पिक्चर\" के अंदर है"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"अगर आप नहीं चाहते कि <xliff:g id="NAME">%s</xliff:g> इस सुविधा का उपयोग करे, तो सेटिंग खोलने के लिए टैप करें और उसे बंद करें ."</string>
<string name="pip_play" msgid="3496151081459417097">"चलाएं"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
+ <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रीन डिवाइडर"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"स्क्रीन को बाएं हिस्से में स्प्लिट करें"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"स्क्रीन को दाएं हिस्से में स्प्लिट करें"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"स्क्रीन को ऊपर के हिस्से में स्प्लिट करें"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"स्क्रीन को सबसे नीचे वाले हिस्से में स्प्लिट करें"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"वन-हैंडेड मोड का इस्तेमाल करना"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"इस मोड से बाहर निकलने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के बाहर कहीं भी टैप करें"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"वन-हैंडेड मोड चालू करें"</string>
@@ -61,24 +68,46 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"सबसे नीचे दाईं ओर ले जाएं"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> की सेटिंग"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारिज करें"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"बातचीत को बबल न करें"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"बबल्स का इस्तेमाल करके चैट करें"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"नई बातचीत फ़्लोटिंग आइकॉन या बबल्स की तरह दिखेंगी. बबल को खोलने के लिए टैप करें. इसे एक जगह से दूसरी जगह ले जाने के लिए खींचें और छोड़ें."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"जब चाहें, बबल्स को कंट्रोल करें"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"इस ऐप्लिकेशन पर बबल्स को बंद करने के लिए \'प्रबंधित करें\' पर टैप करें"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"इस ऐप्लिकेशन पर बबल्स को बंद करने के लिए \'मैनेज करें\' पर टैप करें"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ठीक है"</string>
- <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के बबल्स मौजूद नहीं हैं"</string>
+ <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के कोई बबल्स नहीं हैं"</string>
<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="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"टैप करके ऐप्लिकेशन को रीस्टार्ट करें और बेहतर व्यू पाएं."</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_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, आपने जो बदलाव सेव नहीं किए हैं या अब तक जो काम किए हैं उनका डेटा, ऐप्लिकेशन रीस्टार्ट करने पर मिट सकता है"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करें"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
+ <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ऐप्लिकेशन आइकॉन"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
+ <string name="select_text" msgid="5139083974039906583">"चुनें"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
+ <string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index d897ac73f80d..595435bcf8b8 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"पिक्चर में पिक्चर"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string>
+ <string name="pip_close" msgid="2955969519031223530">"बंद करें"</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>
+ <string name="pip_move" msgid="158770205886688553">"ले जाएं"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"बड़ा करें"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"छोटा करें"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"कंट्रोल मेन्यू पर जाने के लिए "<annotation icon="home_icon">" होम"</annotation>" को दो बार दबाएं"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"पिक्चर में पिक्चर मेन्यू."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"बाईं ओर ले जाएं"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"दाईं ओर ले जाएं"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ऊपर ले जाएं"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"नीचे ले जाएं"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"हो गया"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 5ecc5585a6e9..f0a8deebccf4 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Postavke"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Otvorite podijeljeni zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izbornik"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izbornik slike u slici"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> jest na slici u slici"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproduciraj"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog zaslona"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevi zaslon u cijeli zaslon"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevi zaslon na 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji zaslon na 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji zaslon u cijeli zaslon"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Podijeli lijevo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Podijeli desno"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Podijeli gore"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podijeli dolje"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premjestite u donji desni kut"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Zaustavi razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Oblačići u chatu"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori pojavljuju se kao pomične ikone ili oblačići. Dodirnite za otvaranje oblačića. Povucite da biste ga premjestili."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste ponovo pokrenuli tu aplikaciju i prikazali je na cijelom zaslonu."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</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_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li ponovno pokrenuti za bolji pregled?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovno pokrenuti aplikaciju tako da bolje izgleda na zaslonu, no mogli biste izgubiti napredak ili sve nespremljene promjene"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
+ <string name="select_text" msgid="5139083974039906583">"Odaberite"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Snimka zaslona"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
index 8f5f3164c4d7..965b9b8c31c6 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
@@ -19,10 +19,16 @@
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="2955969519031223530">"Zatvori"</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>
+ <string name="pip_move" msgid="158770205886688553">"Premjesti"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Sažmi"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Dvaput pritisnite "<annotation icon="home_icon">"POČETNI ZASLON"</annotation>" za kontrole"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Izbornik slike u slici."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pomaknite ulijevo"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pomaknite udesno"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pomaknite prema gore"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pomaknite prema dolje"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotovo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 2295250e2853..ea43370d7581 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Beállítások"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Váltás osztott képernyőre"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Kép a képben menü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A(z) <xliff:g id="NAME">%s</xliff:g> kép a képben funkciót használ"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Lejátszás"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Elválasztó az osztott nézetben"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Bal oldali teljes képernyőre"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Bal oldali 70%-ra"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Felső 30%-ra"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alsó teljes képernyőre"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Osztás a képernyő bal oldalán"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Osztás a képernyő jobb oldalán"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Osztás a képernyő tetején"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Osztás alul"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Egykezes mód használata"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Egykezes mód indítása"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Áthelyezés le és jobbra"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> beállításai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Buborék elvetése"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne jelenjen meg a beszélgetés buborékban"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Buborékokat használó csevegés"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Az új beszélgetések lebegő ikonként, vagyis buborékként jelennek meg. A buborék megnyitásához koppintson rá. Áthelyezéshez húzza a kívánt helyre."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Buborék"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kezelés"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Buborék elvetve."</string>
- <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="restart_button_description" msgid="6712141648865547958">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</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_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Újraindítja a jobb megjelenítés érdekében?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Újraindíthatja az alkalmazást a képernyőn való jobb megjelenítés érdekében, de elveszítheti az előrehaladását és az esetleges nem mentett változásokat"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Alkalmazásikon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
+ <string name="select_text" msgid="5139083974039906583">"Kiválasztás"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Képernyőkép"</string>
+ <string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
index fc8d79589121..90cbfe643c82 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Kép a képben"</string>
<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_close" msgid="2955969519031223530">"Bezárás"</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>
+ <string name="pip_move" msgid="158770205886688553">"Áthelyezés"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Kibontás"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Összecsukás"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Vezérlők: A "<annotation icon="home_icon">"KEZDŐKÉPERNYŐ"</annotation>" gomb kétszeri megnyomása"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Kép a képben menü."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mozgatás balra"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mozgatás jobbra"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mozgatás felfelé"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mozgatás lefelé"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Kész"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 208936539094..4ea668c93f98 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Կարգավորումներ"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Մտնել տրոհված էկրանի ռեժիմ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ընտրացանկ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"«Նկար նկարի մեջ» ռեժիմի ընտրացանկ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>-ը «Նկար նկարի մեջ» ռեժիմում է"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Եթե չեք ցանկանում, որ <xliff:g id="NAME">%s</xliff:g>-ն օգտագործի այս գործառույթը, հպեք՝ կարգավորումները բացելու և այն անջատելու համար։"</string>
<string name="pip_play" msgid="3496151081459417097">"Նվագարկել"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Տրոհված էկրանի բաժանիչ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Հավելվածը ձախ կողմում"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Հավելվածը աջ կողմում"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Հավելվածը վերևում"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Հավելվածը ներքևում"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ինչպես օգտվել մեկ ձեռքի ռեժիմից"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Գործարկել մեկ ձեռքի ռեժիմը"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Տեղափոխել ներքև՝ աջ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – կարգավորումներ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Փակել ամպիկը"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Հպեք՝ հավելվածը վերագործարկելու և լիաէկրան ռեժիմին անցնելու համար։"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար։"</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_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Վերագործարկե՞լ հավելվածը"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Դուք կարող եք վերագործարկել հավելվածը, որպեսզի այն ավելի լավ ցուցադրվի ձեր էկրանին, սակայն ձեր առաջընթացը և չպահված փոփոխությունները կկորեն"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Չեղարկել"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Հավելվածի պատկերակ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
+ <string name="select_text" msgid="5139083974039906583">"Ընտրել"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Սքրինշոթ"</string>
+ <string name="close_text" msgid="4986518933445178928">"Փակել"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
index f5665b8dd166..30b5911147b5 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Նկար նկարի մեջ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Փակել"</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>
+ <string name="pip_move" msgid="158770205886688553">"Տեղափոխել"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Ծավալել"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Ծալել"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Կարգավորումների համար կրկնակի սեղմեք "<annotation icon="home_icon">"ԳԼԽԱՎՈՐ ԷԿՐԱՆ"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"«Նկար նկարի մեջ» ռեժիմի ընտրացանկ։"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Տեղափոխել ձախ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Տեղափոխել աջ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Տեղափոխել վերև"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Տեղափոխել ներքև"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Պատրաստ է"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 1b46b2fe2570..df2f2df6df19 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Setelan"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk ke mode layar terpisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> adalah picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Putar"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Pembagi layar terpisah"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Layar penuh di kiri"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Layar penuh di bawah"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Pisahkan ke kiri"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Pisahkan ke kanan"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Pisahkan ke atas"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisahkan ke bawah"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mode satu tangan"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulai mode satu tangan"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pindahkan ke kanan bawah"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setelan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Tutup balon"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan gunakan percakapan balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat dalam tampilan balon"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Percakapan baru muncul sebagai ikon mengambang, atau balon. Ketuk untuk membuka balon. Tarik untuk memindahkannya."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Kelola"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon ditutup."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Ketuk untuk memulai ulang aplikasi ini dan membuka layar penuh."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik."</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_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikon Aplikasi"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
+ <string name="select_text" msgid="5139083974039906583">"Pilih"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Tutup"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
index a1535653f679..0fda69f6c0e4 100644
--- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Tutup"</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>
+ <string name="pip_move" msgid="158770205886688553">"Pindahkan"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Luaskan"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Ciutkan"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Tekan dua kali "<annotation icon="home_icon">"HOME"</annotation>" untuk membuka kontrol"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Picture-in-Picture."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pindahkan ke kiri"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pindahkan ke kanan"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pindahkan ke atas"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pindahkan ke bawah"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Selesai"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index a201c95137f3..a6301b3ce0ed 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Stillingar"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Opna skjáskiptingu"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Valmynd"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Valmynd fyrir mynd í mynd"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er með mynd í mynd"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Spila"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Skjáskipting"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vinstri á öllum skjánum"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vinstri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Efri 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Neðri á öllum skjánum"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Skipta vinstra megin"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Skipta hægra megin"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Skipta efst"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Skipta neðst"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Notkun einhentrar stillingar"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ræsa einhenta stillingu"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Færðu neðst til hægri"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Stillingar <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Loka blöðru"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ekki setja samtal í blöðru"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Spjalla með blöðrum"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Ný samtöl birtast sem fljótandi tákn eða blöðrur. Ýttu til að opna blöðru. Dragðu hana til að færa."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Ýttu til að endurræsa forritið og sýna það á öllum skjánum."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</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_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Viltu endurræsa til að fá betri sýn?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Þú getur endurræst forritið svo það falli betur að skjánum en þú gætir tapað framvindunni eða óvistuðum breytingum"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Hætta við"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Tákn forrits"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
+ <string name="select_text" msgid="5139083974039906583">"Velja"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skjámynd"</string>
+ <string name="close_text" msgid="4986518933445178928">"Loka"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
index 70ca1afe3aea..e0d604f30d1a 100644
--- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Mynd í mynd"</string>
<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_close" msgid="2955969519031223530">"Loka"</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>
+ <string name="pip_move" msgid="158770205886688553">"Færa"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Stækka"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Minnka"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Ýttu tvisvar á "<annotation icon="home_icon">"HEIM"</annotation>" til að opna stillingar"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Valmynd fyrir mynd í mynd."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Færa til vinstri"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Færa til hægri"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Færa upp"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Færa niður"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Lokið"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 7157ed088d30..37551161f8d8 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Impostazioni"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Accedi a schermo diviso"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Picture in picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> è in Picture in picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Riproduci"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Strumento per schermo diviso"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Schermata sinistra a schermo intero"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Schermata sinistra al 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Schermata superiore al 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Schermata inferiore a schermo intero"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividi a sinistra"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividi a destra"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividi in alto"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividi in basso"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usare la modalità a una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Avvia la modalità a una mano"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sposta in basso a destra"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Impostazioni <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora bolla"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mettere la conversazione nella bolla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatta utilizzando le bolle"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Le nuove conversazioni vengono mostrate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Fumetto ignorato."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tocca per riavviare l\'app e passare alla modalità a schermo intero."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tocca per riavviare quest\'app per una migliore visualizzazione."</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_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trascina in un\'altra app per usare lo schermo diviso"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icona dell\'app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seleziona"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Chiudi"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
index cda627517872..267f67463917 100644
--- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture in picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Chiudi"</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>
+ <string name="pip_move" msgid="158770205886688553">"Sposta"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Espandi"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Comprimi"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Premi due volte "<annotation icon="home_icon">"HOME"</annotation>" per accedere ai controlli"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Picture in picture."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sposta a sinistra"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sposta a destra"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Sposta su"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Sposta giù"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Fine"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 52a6b0676222..b9259703daa5 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"כניסה למסך המפוצל"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"תפריט \'תמונה בתוך תמונה\'"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולהשבית את התכונה."</string>
<string name="pip_play" msgid="3496151081459417097">"הפעלה"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
<string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
+ <string name="divider_title" msgid="5482989479865361192">"מחלק מסך מפוצל"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"פיצול שמאלה"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"פיצול ימינה"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"פיצול למעלה"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"פיצול למטה"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש בתכונה \'מצב שימוש ביד אחת\'"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"העברה לפינה הימנית התחתונה"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"הגדרות <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"סגירת בועה"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"צריך להקיש כדי להפעיל מחדש את האפליקציה הזו ולעבור למסך מלא."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש."</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_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"להפעיל מחדש לתצוגה טובה יותר?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין צורך להציג את זה שוב"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
+ <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"סמל האפליקציה"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
+ <string name="select_text" msgid="5139083974039906583">"בחירה"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"צילום מסך"</string>
+ <string name="close_text" msgid="4986518933445178928">"סגירה"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 30ce97b998ca..6b30f5642ad3 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"תמונה בתוך תמונה"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
- <string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"סגירה"</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>
+ <string name="pip_move" msgid="158770205886688553">"העברה"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"הרחבה"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"כיווץ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"לחיצה כפולה על "<annotation icon="home_icon">"בית"</annotation>" תציג את אמצעי הבקרה"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"תפריט \'תמונה בתוך תמונה\'."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"הזזה שמאלה"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"הזזה ימינה"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"הזזה למעלה"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"הזזה למטה"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"סיום"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 5a25c24ba034..b7c30a387eed 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"分割画面に切り替え"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"メニュー"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ピクチャー イン ピクチャーのメニュー"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>はピクチャー イン ピクチャーで表示中です"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>でこの機能を使用しない場合は、タップして設定を開いて OFF にしてください。"</string>
<string name="pip_play" msgid="3496151081459417097">"再生"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
+ <string name="divider_title" msgid="5482989479865361192">"分割画面の分割線"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"左に分割"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"右に分割"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"上に分割"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"下に分割"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"片手モードの使用"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"片手モードを開始します"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"右下に移動"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> の設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"バブルを閉じる"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"タップしてこのアプリを再起動すると、全画面表示になります。"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</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_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"キャンセル"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"アプリのアイコン"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"その他"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
+ <string name="select_text" msgid="5139083974039906583">"選択"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"スクリーンショット"</string>
+ <string name="close_text" msgid="4986518933445178928">"閉じる"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
index e58e7bf6fabc..2a79e3c651a1 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ピクチャー イン ピクチャー"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string>
+ <string name="pip_close" msgid="2955969519031223530">"閉じる"</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>
+ <string name="pip_move" msgid="158770205886688553">"移動"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"開く"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"閉じる"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"コントロールにアクセス: "<annotation icon="home_icon">" ホーム "</annotation>" を 2 回押します"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ピクチャー イン ピクチャーのメニューです。"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"左に移動"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"右に移動"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"上に移動"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"下に移動"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完了"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index bff86fa6ffe2..e1d1de4aad99 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"პარამეტრები"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"გაყოფილ ეკრანში შესვლა"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"მენიუ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"„ეკრანი ეკრანში“ რეჟიმის მენიუ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> იყენებს რეჟიმს „ეკრანი ეკრანში“"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"თუ არ გსურთ, რომ <xliff:g id="NAME">%s</xliff:g> ამ ფუნქციას იყენებდეს, აქ შეხებით შეგიძლიათ გახსნათ პარამეტრები და გამორთოთ ის."</string>
<string name="pip_play" msgid="3496151081459417097">"დაკვრა"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
<string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
+ <string name="divider_title" msgid="5482989479865361192">"ეკრანის გაყოფის გამყოფი"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"გაყოფა მარცხნივ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"გაყოფა მარჯვნივ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"გაყოფა ზემოთ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"გაყოფა ქვემოთ"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ცალი ხელის რეჟიმის გამოყენება"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ცალი ხელის რეჟიმის დაწყება"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"გადაანაცვ. ქვემოთ და მარჯვნივ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-ის პარამეტრები"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ბუშტის დახურვა"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"შეეხეთ ამ აპის გადასატვირთად და გადადით სრულ ეკრანზე."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"შეეხეთ, რომ გადატვირთოთ ეს აპი უკეთესი ხედისთვის."</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_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"გსურთ გადატვირთვა უკეთესი ხედისთვის?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"შეგიძლიათ გადატვირთოთ აპი იმისათვის, რომ თქვენს ეკრანზე უკეთესად გამოჩნდეს, თუმცა თქვენ მიერ შესრულებული მოქმედებები შეიძლება დაიკარგოს ან ცვლილებების შენახვა ვერ მოხერხდეს"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"გაუქმება"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
+ <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"აპის ხატულა"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
+ <string name="select_text" msgid="5139083974039906583">"არჩევა"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ეკრანის ანაბეჭდი"</string>
+ <string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
index b09686646c8b..58bae02120ff 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ეკრანი ეკრანში"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string>
+ <string name="pip_close" msgid="2955969519031223530">"დახურვა"</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>
+ <string name="pip_move" msgid="158770205886688553">"გადაადგილება"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"გაშლა"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ჩაკეცვა"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"სახლის მართვის საშუალებებზე წვდომისთვის ორმაგად დააჭირეთ "<annotation icon="home_icon">" მთავარ ღილაკს "</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"მენიუ „ეკრანი ეკრანში“."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"მარცხნივ გადატანა"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"მარჯვნივ გადატანა"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ზემოთ გადატანა"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ქვემოთ გადატანა"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"მზადაა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index f57f3f581c85..de952add248a 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Бөлінген экранға кіру"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Mәзір"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"Сурет ішіндегі сурет\" мәзірі"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"суреттегі сурет\" режимінде"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> деген пайдаланушының бұл мүмкіндікті пайдалануын қаламасаңыз, параметрлерді түртіп ашыңыз да, оларды өшіріңіз."</string>
<string name="pip_play" msgid="3496151081459417097">"Ойнату"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Бөлінген экран бөлгіші"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Сол жақтан шығару"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Оң жақтан шығару"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Жоғарыдан шығару"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Астынан шығару"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бір қолмен енгізу режимін пайдалану"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Шығу үшін экранның төменгі жағынан жоғары қарай сырғытыңыз немесе қолданбаның үстінен кез келген жерден түртіңіз."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бір қолмен енгізу режимін іске қосу"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төменгі оң жаққа жылжыту"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлері"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Қалқымалы хабарды жабу"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Бұл қолданбаны қайта қосып, толық экранға өту үшін түртіңіз."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</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_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Көріністі жақсарту үшін өшіріп қосу керек пе?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Экранда жақсырақ көрінуі үшін қолданбаны өшіріп қосуыңызға болады, бірақ мұндайда ағымдағы прогресс өшіп қалуы немесе сақталмаған өзгерістерді жоғалтуыңыз мүмкін."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Бас тарту"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Қолданба белгішесі"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
+ <string name="select_text" msgid="5139083974039906583">"Таңдау"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+ <string name="close_text" msgid="4986518933445178928">"Жабу"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
index 7bade0dff0d9..df5f6171b11b 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Суреттегі сурет"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Жабу"</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>
+ <string name="pip_move" msgid="158770205886688553">"Жылжыту"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Жаю"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Жию"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Басқару: "<annotation icon="home_icon">" НЕГІЗГІ ЭКРАН "</annotation>" түймесін екі рет басыңыз."</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"\"Сурет ішіндегі сурет\" мәзірі."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Солға жылжыту"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Оңға жылжыту"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Жоғары жылжыту"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Төмен жылжыту"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Дайын"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 5c04f881fe0e..762f3a84ad5b 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ការកំណត់"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ចូលមុខងារ​បំបែកអេក្រង់"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ម៉ឺនុយ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ម៉ឺនុយ​រូប​ក្នុងរូប"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ស្ថិតក្នុងមុខងាររូបក្នុងរូប"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ប្រសិនបើ​អ្នក​មិន​ចង់​ឲ្យ <xliff:g id="NAME">%s</xliff:g> ប្រើ​មុខងារ​នេះ​ សូមចុច​​បើក​ការកំណត់ រួច​បិទ​វា។"</string>
<string name="pip_play" msgid="3496151081459417097">"លេង"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះ​ប្រហែល​ជាមិនដំណើរការ​នៅលើ​អេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធី​នេះមិន​អាច​ចាប់ផ្តើម​នៅលើ​អេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
<string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
+ <string name="divider_title" msgid="5482989479865361192">"បន្ទាត់ខណ្ឌចែកអេក្រង់បំបែក"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"បំបែក​ខាងឆ្វេង"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"បំបែក​ខាងស្ដាំ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"បំបែក​ខាងលើ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"បំបែក​ខាងក្រោម"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"កំពុងប្រើ​មុខងារប្រើដៃម្ខាង"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ដើម្បីចាកចេញ សូមអូសឡើងលើ​ពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយ​នៅខាងលើកម្មវិធី"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ចាប់ផ្ដើម​មុខងារប្រើដៃម្ខាង"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ផ្លាស់ទីទៅផ្នែកខាងក្រោម​ខាងស្ដាំ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ការកំណត់ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ច្រានចោល​ពពុះ"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោល​សារលេចឡើង។"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ចុចដើម្បី​ចាប់ផ្ដើម​កម្មវិធី​នេះឡើងវិញ រួចចូលប្រើ​ពេញអេក្រង់។"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ចុចដើម្បី​ចាប់ផ្ដើម​កម្មវិធី​នេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ។"</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_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដង​នៅ​ក្រៅ​កម្មវិធី ដើម្បី​ប្ដូរ​ទីតាំង​កម្មវិធី​នោះ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ចាប់ផ្ដើម​ឡើងវិញ ដើម្បីទទួលបានទិដ្ឋភាពកាន់តែស្អាតឬ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"អ្នកអាចចាប់ផ្ដើម​កម្មវិធីឡើងវិញ ដើម្បីឱ្យកម្មវិធីនេះមើលទៅស្អាតជាងមុននៅលើអេក្រង់របស់អ្នក ប៉ុន្តែអ្នកអាចនឹងបាត់បង់ដំណើរការរបស់អ្នក ឬការកែប្រែណាមួយដែលអ្នកមិនបានរក្សាទុក"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"បោះបង់"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើម​ឡើង​វិញ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"រូប​កម្មវិធី"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់​ពេញ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"មុខងារ​បំបែក​អេក្រង់"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
+ <string name="select_text" msgid="5139083974039906583">"ជ្រើសរើស"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"រូបថតអេក្រង់"</string>
+ <string name="close_text" msgid="4986518933445178928">"បិទ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
index 721be1fc1650..a3c7e22f268a 100644
--- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"រូបក្នុងរូប"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធី​គ្មានចំណងជើង)"</string>
- <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"បិទ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ផ្លាស់ទី"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ពង្រីក"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"បង្រួម"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ចុចពីរដងលើ"<annotation icon="home_icon">"ប៊ូតុងដើម"</annotation>" ដើម្បីបើកផ្ទាំងគ្រប់គ្រង"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ម៉ឺនុយ​រូប​ក្នុងរូប"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ផ្លាស់ទី​ទៅ​ឆ្វេង"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ផ្លាស់ទីទៅ​ស្តាំ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ផ្លាស់ទី​ឡើង​លើ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ផ្លាស់ទី​ចុះ​ក្រោម"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"រួចរាល់"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index e91383caa009..f9582ffbc39c 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ಸ್ಪ್ಲಿಟ್‌-ಸ್ಕ್ರೀನ್‌ಗೆ ಪ್ರವೇಶಿಸಿ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವಾಗಿದೆ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ವೈಶಿಷ್ಟ್ಯ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="pip_play" msgid="3496151081459417097">"ಪ್ಲೇ"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್‌ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
+ <string name="divider_title" msgid="5482989479865361192">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ಎಡಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ಬಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ಮೇಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"ಕೆಳಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ಒಂದು ಕೈ ಮೋಡ್ ಬಳಸುವುದರ ಬಗ್ಗೆ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್‌ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ಬಬಲ್ ವಜಾಗೊಳಿಸಿ"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</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_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬಹುದು, ಇದರಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಉತ್ತಮವಾಗಿ ಕಾಣಿಸುತ್ತದೆ, ಆದರೆ ನಿಮ್ಮ ಪ್ರಗತಿಯನ್ನು ಅಥವಾ ಯಾವುದೇ ಉಳಿಸದ ಬದಲಾವಣೆಗಳನ್ನು ನೀವು ಕಳೆದುಕೊಳ್ಳಬಹುದು"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ರದ್ದುಮಾಡಿ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್‌ಸ್ಕ್ರೀನ್"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್‌ಟಾಪ್ ಮೋಡ್"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
+ <string name="select_text" msgid="5139083974039906583">"ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್"</string>
+ <string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 8310c8a1169c..3dfe573a6506 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ಮುಚ್ಚಿರಿ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ಸರಿಸಿ"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ಕುಗ್ಗಿಸಿ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ಕಂಟ್ರೋಲ್‌ಗಳಿಗಾಗಿ "<annotation icon="home_icon">"ಹೋಮ್"</annotation>" ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿರಿ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ಮೇಲಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ಕೆಳಗೆ ಸರಿಸಿ"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ಮುಗಿದಿದೆ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 104ba3f22c96..2b30aabab88f 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"설정"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"화면 분할 모드로 전환"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"메뉴"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"PIP 모드 메뉴"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>에서 PIP 사용 중"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>에서 이 기능이 사용되는 것을 원하지 않는 경우 탭하여 설정을 열고 기능을 사용 중지하세요."</string>
<string name="pip_play" msgid="3496151081459417097">"재생"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
<string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
+ <string name="divider_title" msgid="5482989479865361192">"화면 분할기"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"왼쪽으로 분할"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"오른쪽으로 분할"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"위쪽으로 분할"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"아래쪽으로 분할"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"한 손 사용 모드 사용하기"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"한 손 사용 모드 시작"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"오른쪽 하단으로 이동"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> 설정"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"대화창 닫기"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"탭하여 이 앱을 다시 시작하고 전체 화면으로 이동합니다."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</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_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"화면에 맞게 보도록 다시 시작할까요?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"앱을 다시 시작하면 화면에 더 잘 맞게 볼 수는 있지만 진행 상황 또는 저장되지 않은 변경사항을 잃을 수도 있습니다."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"취소"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
+ <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"앱 아이콘"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
+ <string name="select_text" msgid="5139083974039906583">"선택"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"스크린샷"</string>
+ <string name="close_text" msgid="4986518933445178928">"닫기"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
index a3e055a515a1..969a68d0346e 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"PIP 모드"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string>
+ <string name="pip_close" msgid="2955969519031223530">"닫기"</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>
+ <string name="pip_move" msgid="158770205886688553">"이동"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"펼치기"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"접기"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"제어 메뉴에 액세스하려면 "<annotation icon="home_icon">"홈"</annotation>"을 두 번 누르세요."</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"PIP 모드 메뉴입니다."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"왼쪽으로 이동"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"오른쪽으로 이동"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"위로 이동"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"아래로 이동"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"완료"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 8203622a33fc..27b89b7e869f 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -19,9 +19,10 @@
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_menu_accessibility_title" msgid="8129016817688656249">"Сүрөт ичиндеги сүрөт менюсу"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, жөндөөлөрдү ачып туруп, аны өчүрүп коюңуз."</string>
<string name="pip_play" msgid="3496151081459417097">"Ойнотуу"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Экранды бөлгүч"</string>
<string name="accessibility_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>
@@ -46,21 +49,27 @@
<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_split_left" msgid="1713683765575562458">"Солго бөлүү"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Оңго бөлүү"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Өйдө бөлүү"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ылдый бөлүү"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бир кол режимин колдонуу"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Бир кол режиминен чыгуу"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер жөндөөлөрү"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер параметрлери"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Кошумча меню"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Кайра топтомго кошуу"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="APP_NAME">%2$s</xliff:g> жана дагы <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> колдонмодон <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Жогорку сол жакка жылдыруу"</string>
- <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдырыңыз"</string>
+ <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="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string>
+ <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Бул колдонмону өчүрүп күйгүзүп, толук экранга өтүү үчүн таптап коюңуз."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз."</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_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Жакшыраак көрүү үчүн өчүрүп күйгүзөсүзбү?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Экранда жакшыраак көрүү үчүн колдонмону өчүрүп күйгүзө аласыз, бирок аткарылган иш же сакталбаган өзгөрүүлөр өчүрүлүшү мүмкүн"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Токтотуу"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Колдонмонун сүрөтчөсү"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
+ <string name="select_text" msgid="5139083974039906583">"Тандоо"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+ <string name="close_text" msgid="4986518933445178928">"Жабуу"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
index 887ac52c8e43..68262e521f4a 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Сүрөттөгү сүрөт"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Жабуу"</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>
+ <string name="pip_move" msgid="158770205886688553">"Жылдыруу"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Жайып көрсөтүү"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Жыйыштыруу"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Башкаруу элементтерин ачуу үчүн "<annotation icon="home_icon">" БАШКЫ БЕТ "</annotation>" баскычын эки жолу басыңыз"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Сүрөт ичиндеги сүрөт менюсу."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Солго жылдыруу"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Оңго жылдыруу"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Жогору жылдыруу"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Төмөн жылдыруу"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Бүттү"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 24396786f9d8..6aae84c3df2b 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ການຕັ້ງຄ່າ"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ເຂົ້າການແບ່ງໜ້າຈໍ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ເມນູ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ເມນູການສະແດງຜົນຊ້ອນກັນ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ແມ່ນເປັນການສະແດງຜົນຫຼາຍຢ່າງພ້ອມກັນ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ຫາກທ່ານບໍ່ຕ້ອງການ <xliff:g id="NAME">%s</xliff:g> ໃຫ້ໃຊ້ຄຸນສົມບັດນີ້, ໃຫ້ແຕະເພື່ອເປີດການຕັ້ງຄ່າ ແລ້ວປິດມັນໄວ້."</string>
<string name="pip_play" msgid="3496151081459417097">"ຫຼິ້ນ"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ແຍກຊ້າຍ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ແຍກຂວາ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ແຍກເທິງ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"ແຍກລຸ່ມ"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ກຳລັງໃຊ້ໂໝດມືດຽວ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເໜືອແອັບ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ເລີ່ມໂໝດມືດຽວ"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ຍ້າຍຂວາລຸ່ມ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ການຕັ້ງຄ່າ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ປິດຟອງໄວ້"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ຟອງ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ຈັດການ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ປິດ Bubble ໄສ້ແລ້ວ."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ ແລະ ໃຊ້ແບບເຕັມຈໍ."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ."</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_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ຣີສະຕາດເພື່ອໃຫ້ມີມຸມມອງທີ່ດີຂຶ້ນບໍ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ທ່ານສາມາດຣີສະຕາດແອັບໄດ້ເພື່ອໃຫ້ມັນເບິ່ງດີຂຶ້ນໃນໜ້າຈໍຂອງທ່ານ ແຕ່ທ່ານອາດຈະສູນເສຍຄວາມຄືບໜ້າ ຫຼື ການປ່ຽນແປງທີ່ບໍ່ໄດ້ບັນທຶກໄວ້"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ຍົກເລີກ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ໄອຄອນແອັບ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ໂໝດເດັສທັອບ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string>
+ <string name="select_text" msgid="5139083974039906583">"ເລືອກ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ຮູບໜ້າຈໍ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
index 91c4a033356d..b84c83555ea0 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ການສະແດງຜົນຊ້ອນກັນ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string>
- <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ປິດ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ຍ້າຍ"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ຂະຫຍາຍ"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ຫຍໍ້"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ກົດ "<annotation icon="home_icon">"HOME"</annotation>" ສອງເທື່ອສຳລັບການຄວບຄຸມ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ເມນູການສະແດງຜົນຊ້ອນກັນ."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ຍ້າຍໄປຊ້າຍ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ຍ້າຍໄປຂວາ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ຍ້າຍຂຶ້ນ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ຍ້າຍລົງ"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ແລ້ວໆ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index e2ae643ad308..3f2a17be4971 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Nustatymai"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Įjungti išskaidyto ekrano režimą"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Vaizdo vaizde meniu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> rodom. vaizdo vaizde"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Leisti"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Skaidyto ekrano daliklis"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kairysis ekranas viso ekrano režimu"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kairysis ekranas 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Viršutinis ekranas 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apatinis ekranas viso ekrano režimu"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Išskaidyti kairėn"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Išskaidyti dešinėn"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Išskaidyti viršuje"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Išskaidyti apačioje"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienos rankos režimo naudojimas"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pradėti vienos rankos režimą"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Perkelti į apačią dešinėje"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ nustatymai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Atsisakyti burbulo"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerodyti pokalbio burbule"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Pokalbis naudojant burbulus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nauji pokalbiai rodomi kaip slankiosios piktogramos arba burbulai. Palieskite, kad atidarytumėte burbulą. Vilkite, kad perkeltumėte."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Debesėlis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Tvarkyti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Debesėlio atsisakyta."</string>
- <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="restart_button_description" msgid="6712141648865547958">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau."</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_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Paleisti iš naujo, kad būtų geresnis vaizdas?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Galite iš naujo paleisti programą, kad ji geriau atrodytų ekrane, bet galite prarasti eigą ir neišsaugotus pakeitimus"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Programos piktograma"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
+ <string name="select_text" msgid="5139083974039906583">"Pasirinkti"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Ekrano kopija"</string>
+ <string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
index 04265ca01b48..0537553cc36a 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Vaizdas vaizde"</string>
<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_close" msgid="2955969519031223530">"Uždaryti"</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>
+ <string name="pip_move" msgid="158770205886688553">"Perkelti"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Išskleisti"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Sutraukti"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Jei reikia valdiklių, dukart pasp. "<annotation icon="home_icon">"PAGRINDINIS"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Vaizdo vaizde meniu."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Perkelti kairėn"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Perkelti dešinėn"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Perkelti aukštyn"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Perkelti žemyn"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Atlikta"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index a77160bc262a..ae85c1f6b492 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Iestatījumi"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Piekļūt ekrāna sadalīšanas režīmam"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Izvēlne"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Izvēlne attēlam attēlā"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ir attēlā attēlā"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Atskaņot"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Ekrāna sadalītājs"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kreisā daļa pa visu ekrānu"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pa kreisi 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Augšdaļa 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apakšdaļu pa visu ekrānu"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Sadalījums pa kreisi"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Sadalījums pa labi"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Sadalījums augšdaļā"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Sadalījums apakšdaļā"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienas rokas režīma izmantošana"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pāriet vienas rokas režīmā"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pārvietot apakšpusē pa labi"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Lietotnes <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iestatījumi"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Nerādīt burbuli"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerādīt sarunu burbuļos"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Tērzēšana, izmantojot burbuļus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Jaunas sarunas tiek rādītas kā peldošas ikonas vai burbuļi. Pieskarieties, lai atvērtu burbuli. Velciet, lai to pārvietotu."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Burbulis"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pārvaldīt"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbulis ir noraidīts."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Pieskarieties, lai restartētu šo lietotni un pārietu pilnekrāna režīmā."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</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_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vai restartēt, lai uzlabotu skatu?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Varat restartēt lietotni, lai tā labāk izskatītos ekrānā, taču, iespējams, zaudēsiet paveikto vai nesaglabātas izmaiņas (ja tādas ir)."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Lietotnes ikona"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
+ <string name="select_text" msgid="5139083974039906583">"Atlasīt"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Ekrānuzņēmums"</string>
+ <string name="close_text" msgid="4986518933445178928">"Aizvērt"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
index 8c6191e00833..13baa9bc46eb 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Attēls attēlā"</string>
<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_close" msgid="2955969519031223530">"Aizvērt"</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>
+ <string name="pip_move" msgid="158770205886688553">"Pārvietot"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Izvērst"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Sakļaut"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Atvērt vadīklas: divreiz nospiediet pogu "<annotation icon="home_icon">"SĀKUMS"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Izvēlne attēlam attēlā."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Pārvietot pa kreisi"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Pārvietot pa labi"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Pārvietot augšup"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pārvietot lejup"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gatavs"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index bac0c9eee4c2..133eedb16b64 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Поставки"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Влези во поделен екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени за „Слика во слика“"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> е во слика во слика"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ако не сакате <xliff:g id="NAME">%s</xliff:g> да ја користи функцијава, допрете за да ги отворите поставките и да ја исклучите."</string>
<string name="pip_play" msgid="3496151081459417097">"Пушти"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Разделник на поделен екран"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Подели налево"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Подели надесно"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Подели нагоре"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Подели долу"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Користење на режимот со една рака"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Започни го режимот со една рака"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести долу десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Поставки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отфрли балонче"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Допрете за да ја рестартирате апликацијава и да ја отворите на цел екран."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</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_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Да се рестартира за подобар приказ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Може да ја рестартирате апликацијата за да изгледа подобро на екранот, но може да го изгубите напредокот или незачуваните промени"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Икона на апликацијата"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за компјутер"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string>
+ <string name="select_text" msgid="5139083974039906583">"Изберете"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Слика од екранот"</string>
+ <string name="close_text" msgid="4986518933445178928">"Затворете"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
index beef1fef862b..d7a9516bea7f 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Слика во слика"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Затвори"</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>
+ <string name="pip_move" msgid="158770205886688553">"Премести"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Прошири"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Собери"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Притиснете двапати на "<annotation icon="home_icon">"HOME"</annotation>" за контроли"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Мени за „Слика во слика“."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Премести налево"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Премести надесно"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Премести нагоре"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Премести надолу"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index de0f837fcd3f..c38609edaed8 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ക്രമീകരണം"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"സ്ക്രീൻ വിഭജന മോഡിൽ പ്രവേശിക്കുക"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"മെനു"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ചിത്രത്തിനുള്ളിൽ ചിത്രം മെനു"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ചിത്രത്തിനുള്ളിൽ ചിത്രം രീതിയിലാണ്"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ഈ ഫീച്ചർ ഉപയോഗിക്കേണ്ടെങ്കിൽ, ടാപ്പ് ചെയ്‌ത് ക്രമീകരണം തുറന്ന് അത് ഓഫാക്കുക."</string>
<string name="pip_play" msgid="3496151081459417097">"പ്ലേ ചെയ്യുക"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്‌പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
<string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ഇടത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"വലത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"മുകളിലേക്ക് വിഭജിക്കുക"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"താഴേക്ക് വിഭജിക്കുക"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ഒറ്റക്കൈ മോഡ് ആരംഭിച്ചു"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ക്രമീകരണം"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ബബിൾ ഡിസ്മിസ് ചെയ്യൂ"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ഈ ആപ്പ് റീസ്‌റ്റാർട്ട് ചെയ്‌ത് പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"മികച്ച കാഴ്‌ചയ്‌ക്കായി ഈ ആപ്പ് റീസ്‌റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</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_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"മെച്ചപ്പെട്ട കാഴ്‌ചയ്‌ക്കായി റീസ്റ്റാർട്ട് ചെയ്യണോ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യുകയാണെങ്കിൽ ഇത് നിങ്ങളുടെ സ്‌ക്രീനിൽ മെച്ചപ്പെട്ടതായി കാണും, എന്നാൽ ഇതുവരെയുള്ള പുരോഗതിയും സംരക്ഷിക്കാത്ത മാറ്റങ്ങളും നിങ്ങൾക്ക് നഷ്‌ടമാകും"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"റദ്ദാക്കുക"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ആപ്പ് ഐക്കൺ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്‌ക്ടോപ്പ് മോഡ്"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"സ്‌ക്രീൻ വിഭജനം"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
+ <string name="select_text" msgid="5139083974039906583">"തിരഞ്ഞെടുക്കുക"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"സ്ക്രീൻഷോട്ട്"</string>
+ <string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index c2a532d09647..56f2b196421b 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ചിത്രത്തിനുള്ളിൽ ചിത്രം"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
+ <string name="pip_close" msgid="2955969519031223530">"അടയ്ക്കുക"</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>
+ <string name="pip_move" msgid="158770205886688553">"നീക്കുക"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"വികസിപ്പിക്കുക"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ചുരുക്കുക"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"നിയന്ത്രണങ്ങൾക്കായി "<annotation icon="home_icon">"ഹോം "</annotation>" രണ്ട് തവണ അമർത്തുക"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ചിത്രത്തിനുള്ളിൽ ചിത്രം മെനു."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ഇടത്തേക്ക് നീക്കുക"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"വലത്തേക്ക് നീക്കുക"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"മുകളിലേക്ക് നീക്കുക"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"താഴേക്ക് നീക്കുക"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"പൂർത്തിയായി"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 1205306e0833..e0b414100b9d 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Тохиргоо"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Хуваасан дэлгэцийг оруулна уу"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Цэс"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Дэлгэц доторх дэлгэцийн цэс"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> дэлгэцэн доторх дэлгэцэд байна"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Та <xliff:g id="NAME">%s</xliff:g>-д энэ онцлогийг ашиглуулахыг хүсэхгүй байвал тохиргоог нээгээд, үүнийг унтраана уу."</string>
<string name="pip_play" msgid="3496151081459417097">"Тоглуулах"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
<string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
+ <string name="divider_title" msgid="5482989479865361192">"\"Дэлгэцийг хуваах\" хуваагч"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Зүүн талд хуваах"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Баруун талд хуваах"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Баруун доош зөөх"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-н тохиргоо"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Бөмбөлгийг хаах"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Энэ аппыг дахин эхлүүлж, бүтэн дэлгэцэд орохын тулд товшино уу."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү."</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_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Харагдах байдлыг сайжруулахын тулд дахин эхлүүлэх үү?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Та аппыг дахин эхлүүлэх боломжтой бөгөөд ингэснээр энэ нь таны дэлгэцэд илүү сайн харагдах хэдий ч та явцаа эсвэл хадгалаагүй аливаа өөрчлөлтөө алдаж магадгүй"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Цуцлах"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Aппын дүрс тэмдэг"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
+ <string name="select_text" msgid="5139083974039906583">"Сонгох"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Дэлгэцийн агшин"</string>
+ <string name="close_text" msgid="4986518933445178928">"Хаах"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
index bf8c59b57359..0e6dcca17e38 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Дэлгэц доторх дэлгэц"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Хаах"</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>
+ <string name="pip_move" msgid="158770205886688553">"Зөөх"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Дэлгэх"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Хураах"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Хяналтад хандах бол "<annotation icon="home_icon">"HOME"</annotation>" дээр хоёр дарна уу"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Дэлгэцэн доторх дэлгэцийн цэс."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Зүүн тийш зөөх"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Баруун тийш зөөх"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Дээш зөөх"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Доош зөөх"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Болсон"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index c91d06fdf3d5..f85fe1b06328 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिंग्ज"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रीन एंटर करा"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनू"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"चित्रात-चित्र मेनू"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> चित्रामध्ये चित्र मध्ये आहे"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g>ने हे वैशिष्ट्य वापरू नये असे तुम्हाला वाटत असल्यास, सेटिंग्ज उघडण्यासाठी टॅप करा आणि ते बंद करा."</string>
<string name="pip_play" msgid="3496151081459417097">"प्ले करा"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अ‍ॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अ‍ॅप लाँच होणार नाही."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
+ <string name="divider_title" msgid="5482989479865361192">"स्प्लिट-स्क्रीन विभाजक"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"शीर्ष 10"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तळाशी फुल स्क्रीन"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"डावीकडे स्प्लिट करा"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"उजवीकडे स्प्लिट करा"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"सर्वात वरती स्प्लिट करा"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"खालती स्प्लिट करा"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"एकहाती मोड वापरणे"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲपवर कोठेही टॅप करा"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एकहाती मोड सुरू करा"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"तळाशी उजवीकडे हलवा"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> सेटिंग्ज"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल डिसमिस करा"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"हे अ‍ॅप रीस्टार्ट करण्यासाठी आणि फुल स्क्रीन करण्यासाठी टॅप करा."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"अधिक चांगल्या व्ह्यूसाठी हे अ‍ॅप रीस्टार्ट करण्याकरिता टॅप करा."</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_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"आणखी चांगल्या प्रकारे दिसावे यासाठी रीस्टार्ट करायचे आहे का?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"तुम्ही अ‍ॅप रीस्टार्ट करू शकता, जेणेकरून ते तुमच्या स्क्रीनवर आणखी चांगल्या प्रकारे दिसेल, पण तुमची प्रगती किंवा कोणतेही सेव्ह न केलेले बदल तुम्ही गमवाल"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करा"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
+ <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"अ‍ॅप आयकन"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्‍क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
+ <string name="select_text" msgid="5139083974039906583">"निवडा"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
+ <string name="close_text" msgid="4986518933445178928">"बंद करा"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index 5d519b7afe9a..8a8977979217 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"चित्रात-चित्र"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string>
+ <string name="pip_close" msgid="2955969519031223530">"बंद करा"</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>
+ <string name="pip_move" msgid="158770205886688553">"हलवा"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"विस्तार करा"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"कोलॅप्स करा"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा दाबा"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"चित्रात-चित्र मेनू."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"डावीकडे हलवा"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"उजवीकडे हलवा"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"वर हलवा"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"खाली हलवा"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"पूर्ण झाले"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 652a9919d163..6f225998a319 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Tetapan"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Masuk skrin pisah"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu Gambar dalam Gambar"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> terdapat dalam gambar dalam gambar"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Main"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Pembahagi skrin pisah"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrin penuh kiri"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrin penuh bawah"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Pisah kiri"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Pisah kanan"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Pisah atas"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisah bawah"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mod sebelah tangan"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulakan mod sebelah tangan"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Alihkan ke bawah sebelah kanan"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Tetapan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ketepikan gelembung"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan jadikan perbualan dalam bentuk gelembung"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bersembang menggunakan gelembung"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Perbualan baharu muncul sebagai ikon terapung atau gelembung. Ketik untuk membuka gelembung. Seret untuk mengalihkan gelembung tersebut."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Gelembung"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Urus"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Gelembung diketepikan."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Ketik untuk memulakan semula apl ini dan menggunakan skrin penuh."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik."</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_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Seret apl lain untuk skrin pisah"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulakan semula untuk mendapatkan paparan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda boleh memulakan semula apl supaya apl kelihatan lebih baik pada skrin anda tetapi anda mungkin akan hilang kemajuan anda atau apa-apa perubahan yang belum disimpan"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikon Apl"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mod Desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lagi"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Terapung"</string>
+ <string name="select_text" msgid="5139083974039906583">"Pilih"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Tangkapan skrin"</string>
+ <string name="close_text" msgid="4986518933445178928">"Tutup"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
index 08642c47c91a..afea48d7b510 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Gambar dalam Gambar"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Tutup"</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>
+ <string name="pip_move" msgid="158770205886688553">"Alih"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Kembangkan"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Kuncupkan"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Tekan dua kali "<annotation icon="home_icon">"LAMAN UTAMA"</annotation>" untuk mengakses kawalan"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu Gambar dalam Gambar."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Alih ke kiri"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Alih ke kanan"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Alih ke atas"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Alih ke bawah"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Selesai"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 15d182c6451e..5a88cd098489 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ဆက်တင်များ"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသို့ ဝင်ရန်"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"မီနူး"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"နှစ်ခုထပ်၍ ကြည့်ခြင်းမီနူး"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> သည် နှစ်ခုထပ်၍ကြည့်ခြင်း ဖွင့်ထားသည်"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> အား ဤဝန်ဆောင်မှုကို အသုံးမပြုစေလိုလျှင် ဆက်တင်ကိုဖွင့်ရန် တို့ပြီး ၎င်းဝန်ဆောင်မှုကို ပိတ်လိုက်ပါ။"</string>
<string name="pip_play" msgid="3496151081459417097">"ဖွင့်ရန်"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
+ <string name="divider_title" msgid="5482989479865361192">"မျက်နှာပြင်ခွဲ၍ပြသသည့် စနစ်"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ဘယ်ဘက်ကို ခွဲရန်"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ညာဘက်ကို ခွဲရန်"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ထိပ်ပိုင်းကို ခွဲရန်"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"အောက်ခြေကို ခွဲရန်"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"လက်တစ်ဖက်သုံးမုဒ်ကို စတင်လိုက်သည်"</string>
@@ -61,24 +68,46 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ညာအောက်ခြေသို့ ရွှေ့ပါ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ဆက်တင်များ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ပူဖောင်းကွက် ပယ်ရန်"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"စကားဝိုင်းကို ပူဖောင်းကွက် မပြုလုပ်ပါနှင့်"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ပူဖောင်းကွက် သုံး၍ ချတ်လုပ်ခြင်း"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"ဤအက်ပ်မှနေ၍ ပူဖောင်းများကို ပိတ်ရန်အတွက် \'စီမံရန်\' ကို တို့ပါ"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ရပြီ"</string>
+ <string name="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="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ဤအက်ပ်ကို ပြန်စပြီး ဖန်သားပြင်အပြည့်လုပ်ရန် တို့ပါ။"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့နိုင်သည်။"</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>
+ <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ပိုကောင်းသောမြင်ကွင်းအတွက် ပြန်စမလား။"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"အက်ပ်ကို ပြန်စပါက ၎င်းသည် စခရင်ပေါ်တွင် ပိုကြည့်ကောင်းသွားသော်လည်း သင်၏ လုပ်ငန်းစဉ် (သို့) မသိမ်းရသေးသော အပြောင်းအလဲများကို ဆုံးရှုံးနိုင်သည်"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"မလုပ်တော့"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
+ <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"အက်ပ်သင်္ကေတ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
+ <string name="select_text" msgid="5139083974039906583">"ရွေးရန်"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
index e01daee115ca..f3ed65da43da 100644
--- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"နှစ်ခုထပ်၍ကြည့်ခြင်း"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ပိတ်ရန်"</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>
+ <string name="pip_move" msgid="158770205886688553">"ရွှေ့ရန်"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ချဲ့ရန်"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"လျှော့ပြရန်"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ထိန်းချုပ်မှုအတွက် "<annotation icon="home_icon">" ပင်မခလုတ် "</annotation>" ကို နှစ်ချက်နှိပ်ပါ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"နှစ်ခုထပ်၍ ကြည့်ခြင်းမီနူး။"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ဘယ်သို့ရွှေ့ရန်"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ညာသို့ရွှေ့ရန်"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"အပေါ်သို့ရွှေ့ရန်"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"အောက်သို့ရွှေ့ရန်"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ပြီးပြီ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 9fd42b2f129c..da5f4ca394b1 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Innstillinger"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Aktivér delt skjerm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bilde-i-bilde-meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> er i bilde-i-bilde"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Spill av"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Skilleelement for delt skjerm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Utvid den venstre delen av skjermen til hele skjermen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sett størrelsen på den venstre delen av skjermen til 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Sett størrelsen på den øverste delen av skjermen til 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Utvid den nederste delen av skjermen til hele skjermen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Del opp til venstre"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Del opp til høyre"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Del opp øverst"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Del opp nederst"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bruk av enhåndsmodus"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndsmodus"</string>
@@ -61,10 +68,12 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytt til nederst til høyre"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-innstillinger"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne bobler. Dra for å flytte dem."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontrollér bobler når som helst"</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller bobler når som helst"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Trykk på Administrer for å slå av bobler for denne appen"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Boble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Administrer"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Boblen er avvist."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Trykk for å starte denne appen på nytt og vise den i fullskjerm."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Trykk for å starte denne appen på nytt for bedre visning."</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_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra inn en annen app for å bruke delt skjerm"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vil du starte på nytt for bedre visning?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan starte appen på nytt, slik at den ser bedre ut på skjermen, men du kan miste fremdrift eller ulagrede endringer"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
+ <string name="select_text" msgid="5139083974039906583">"Velg"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string>
+ <string name="close_text" msgid="4986518933445178928">"Lukk"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</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 65ed0b7f5bff..1402e3c9c9bc 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bilde-i-bilde"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Lukk"</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>
+ <string name="pip_move" msgid="158770205886688553">"Flytt"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Vis"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Skjul"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Dobbelttrykk på "<annotation icon="home_icon">"HJEM"</annotation>" for å åpne kontrollene"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Bilde-i-bilde-meny."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flytt til venstre"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flytt til høyre"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flytt opp"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flytt ned"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Ferdig"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 8dfec88cc29d..6b164e96de78 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"सेटिङहरू"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"स्प्लिट स्क्रिन मोड प्रयोग गर्नुहोस्"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"मेनु"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"\"picture-in-picture\" मेनु"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> Picture-in-picture मा छ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"तपाईं <xliff:g id="NAME">%s</xliff:g> ले सुविधा प्रयोग नगरोस् भन्ने चाहनुहुन्छ भने ट्याप गरेर सेटिङहरू खोल्नुहोस् र यसलाई निष्क्रिय पार्नुहोस्।"</string>
<string name="pip_play" msgid="3496151081459417097">"प्ले गर्नुहोस्"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
+ <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रिन डिभाइडर"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"बायाँतिर स्प्लिट गर्नुहोस्"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"दायाँतिर स्प्लिट गर्नुहोस्"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"सिरानतिर स्प्लिट गर्नुहोस्"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"पुछारतिर स्प्लिट गर्नुहोस्"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"एक हाते मोड प्रयोग गरिँदै छ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एक हाते मोड सुरु गर्नुहोस्"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"पुछारमा दायाँतिर सार्नुहोस्"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का सेटिङहरू"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारेज गर्नुहोस्"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस् र फुल स्क्रिन मोडमा जानुहोस्।"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"यो एप अझ राम्रो हेर्न मिल्ने बनाउनका लागि यसलाई रिस्टार्ट गर्न ट्याप गर्नुहोस्।"</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_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"अझ राम्रोसँग देखिने बनाउन एप रिस्टार्ट गर्ने हो?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"यो एप तपाईंको स्क्रिनमा अझ राम्रोसँग देखियोस् भन्नाका लागि तपाईं सो एप रिस्टार्ट गर्न सक्नुहुन्छ तर तपाईंले अहिलेसम्म गरेका क्रियाकलाप वा सेभ गर्न बाँकी परिवर्तनहरू हट्न सक्छन्"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"एपको आइकन"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"थप"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
+ <string name="select_text" msgid="5139083974039906583">"चयन गर्नुहोस्"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"स्क्रिनसट"</string>
+ <string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
index d33fed67efb6..2b1f20fba4e2 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string>
+ <string name="pip_close" msgid="2955969519031223530">"बन्द गर्नुहोस्"</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>
+ <string name="pip_move" msgid="158770205886688553">"सार्नुहोस्"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"एक्स्पान्ड गर्नुहोस्"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"कोल्याप्स गर्नुहोस्"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"कन्ट्रोल मेनु खोल्न "<annotation icon="home_icon">" होम "</annotation>" बटन दुई पटक थिच्नुहोस्"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"\"picture-in-picture\" मेनु।"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"बायाँतिर सार्नुहोस्"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"दायाँतिर सार्नुहोस्"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"माथितिर सार्नुहोस्"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"तलतिर सार्नुहोस्"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"सम्पन्न भयो"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 8468b04c66da..2d02ed5f8f20 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Instellingen"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Gesplitst scherm openen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Scherm-in-scherm-menu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> is in scherm-in-scherm"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Afspelen"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Scheiding voor gesplitst scherm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Linkerscherm op volledig scherm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Linkerscherm 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string>
@@ -46,6 +49,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="accessibility_split_left" msgid="1713683765575562458">"Gesplitst scherm links"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Gesplitst scherm rechts"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Gesplitst scherm boven"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gesplitst scherm onder"</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 1 hand starten"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatten met bubbels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden als zwevende iconen of bubbels getoond. Tik om een bubbel te openen. Sleep om een bubbel te verplaatsen."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tik om deze app opnieuw te starten en te openen op het volledige scherm."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tik om deze app opnieuw op te starten voor een betere weergave."</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_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Opnieuw opstarten voor een betere weergave?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Je kunt de app opnieuw opstarten zodat deze er beter uitziet op je scherm, maar je kunt je voortgang of niet-opgeslagen wijzigingen kwijtraken"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"App-icoon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selecteren"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Sluiten"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
index 9763c5665ab2..6766773fa866 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Scherm-in-scherm"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string>
+ <string name="pip_close" msgid="2955969519031223530">"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>
+ <string name="pip_move" msgid="158770205886688553">"Verplaatsen"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Uitvouwen"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Samenvouwen"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Druk 2 keer op "<annotation icon="home_icon">"HOME"</annotation>" voor bedieningsopties"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Scherm-in-scherm-menu."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Naar links verplaatsen"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Naar rechts verplaatsen"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Omhoog verplaatsen"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Omlaag verplaatsen"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klaar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index a8d8448edf99..64002834b233 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ସେଟିଂସ୍"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ମୋଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ମେନୁ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ପିକଚର-ଇନ-ପିକଚର ମେନୁ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ଏହି ବୈଶିଷ୍ଟ୍ୟ <xliff:g id="NAME">%s</xliff:g> ବ୍ୟବହାର ନକରିବାକୁ ଯଦି ଆପଣ ଚାହାଁନ୍ତି, ସେଟିଙ୍ଗ ଖୋଲିବାକୁ ଟାପ୍‍ କରନ୍ତୁ ଏବଂ ଏହା ଅଫ୍‍ କରିଦିଅନ୍ତୁ।"</string>
<string name="pip_play" msgid="3496151081459417097">"ପ୍ଲେ କରନ୍ତୁ"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍‍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ବାମପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ଡାହାଣପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ଶୀର୍ଷକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"ନିମ୍ନକୁ ସ୍ଲିଟ କରନ୍ତୁ"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ବାହାରି ଯିବା ପାଇଁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ଏକ-ହାତ ମୋଡ୍ ଆରମ୍ଭ କରନ୍ତୁ"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ସେଟିଂସ୍"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ବବଲ୍ ଖାରଜ କରନ୍ତୁ"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ଏହି ଆପକୁ ରିଷ୍ଟାର୍ଟ କରି ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</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_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ରିଷ୍ଟାର୍ଟ କରିବେ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ଆପଣ ଆପକୁ ରିଷ୍ଟାର୍ଟ କରିପାରିବେ ଯାହା ଫଳରେ ଏହା ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଆହୁରି ଭଲ ଦେଖାଯିବ, କିନ୍ତୁ ଆପଣ ଆପଣଙ୍କ ପ୍ରଗତି କିମ୍ବା ସେଭ ହୋଇନଥିବା ଯେ କୌଣସି ପରିବର୍ତ୍ତନ ହରାଇପାରନ୍ତି"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ବାତିଲ କରନ୍ତୁ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ଆପ ଆଇକନ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
+ <string name="select_text" msgid="5139083974039906583">"ଚୟନ କରନ୍ତୁ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ସ୍କ୍ରିନସଟ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
index e0344855bd1f..1e81f4d80f32 100644
--- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ପିକଚର୍-ଇନ୍-ପିକଚର୍"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍‍ ପ୍ରୋଗ୍ରାମ୍‍ ନାହିଁ)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ବନ୍ଦ କରନ୍ତୁ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ମୁଭ କରନ୍ତୁ"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ବିସ୍ତାର କରନ୍ତୁ"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ପାଇଁ "<annotation icon="home_icon">"ହୋମ ବଟନ"</annotation>"କୁ ଦୁଇଥର ଦବାନ୍ତୁ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ପିକଚର-ଇନ-ପିକଚର ମେନୁ।"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ଉପରକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ତଳକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ହୋଇଗଲା"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index f99176cb682d..d39fe95fdc50 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ਸੈਟਿੰਗਾਂ"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"ਮੀਨੂ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਮੀਨੂ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ਤਸਵੀਰ-ਅੰਦਰ-ਤਸਵੀਰ ਵਿੱਚ ਹੈ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ਜੇਕਰ ਤੁਸੀਂ ਨਹੀਂ ਚਾਹੁੰਦੇ ਕਿ <xliff:g id="NAME">%s</xliff:g> ਐਪ ਇਸ ਵਿਸ਼ੇਸ਼ਤਾ ਦੀ ਵਰਤੋਂ ਕਰੇ, ਤਾਂ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਬੰਦ ਕਰੋ।"</string>
<string name="pip_play" msgid="3496151081459417097">"ਚਲਾਓ"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ਖੱਬੇ ਪਾਸੇ ਵੰਡੋ"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"ਸੱਜੇ ਪਾਸੇ ਵੰਡੋ"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ਸਿਖਰ \'ਤੇ ਵੰਡੋ"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"ਹੇਠਾਂ ਵੰਡੋ"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ਇੱਕ ਹੱਥ ਮੋਡ ਸ਼ੁਰੂ ਕਰੋ"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ਸੈਟਿੰਗਾਂ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕਰੋ"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਪੂਰੀ ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਓ।"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</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_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ਕੀ ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ਤੁਸੀਂ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰ ਸਕਦੇ ਹੋ ਤਾਂ ਜੋ ਇਹ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਬਿਹਤਰ ਦਿਸੇ, ਪਰ ਤੁਸੀਂ ਆਪਣੀ ਪ੍ਰਗਤੀ ਜਾਂ ਕਿਸੇ ਅਣਰੱਖਿਅਤ ਤਬਦੀਲੀ ਨੂੰ ਗੁਆ ਸਕਦੇ ਹੋ"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ਰੱਦ ਕਰੋ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ਐਪ ਪ੍ਰਤੀਕ"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
+ <string name="select_text" msgid="5139083974039906583">"ਚੁਣੋ"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
index 9c01ac3f3cc0..758aafad457a 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ਬੰਦ ਕਰੋ"</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>
+ <string name="pip_move" msgid="158770205886688553">"ਲਿਜਾਓ"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ਵਿਸਤਾਰ ਕਰੋ"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ਸਮੇਟੋ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"ਕੰਟਰੋਲਾਂ ਲਈ "<annotation icon="home_icon">"ਹੋਮ"</annotation>" ਨੂੰ ਦੋ ਵਾਰ ਦਬਾਓ"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਮੀਨੂ।"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ਖੱਬੇ ਲਿਜਾਓ"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ਸੱਜੇ ਲਿਜਾਓ"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ਉੱਪਰ ਲਿਜਾਓ"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ਹੇਠਾਂ ਲਿਜਾਓ"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ਹੋ ਗਿਆ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index f2147c04d335..680bd5dd47f6 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ustawienia"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Włącz podzielony ekran"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu funkcji Obraz w obrazie."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Aplikacja <xliff:g id="NAME">%s</xliff:g> działa w trybie obraz w obrazie"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Odtwórz"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Linia dzielenia ekranu"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lewa część ekranu na pełnym ekranie"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% lewej części ekranu"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% górnej części ekranu"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolna część ekranu na pełnym ekranie"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Podziel po lewej"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Podziel po prawej"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Podziel u góry"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podziel u dołu"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korzystanie z trybu jednej ręki"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Uruchom tryb jednej ręki"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Przenieś w prawy dolny róg"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ustawienia"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zamknij dymek"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nie wyświetlaj rozmowy jako dymka"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Czatuj, korzystając z dymków"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nowe rozmowy będą wyświetlane jako pływające ikony lub dymki. Kliknij, by otworzyć dymek. Przeciągnij, by go przenieść."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Dymek"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Zarządzaj"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Zamknięto dymek"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Kliknij, by uruchomić tę aplikację ponownie i przejść w tryb pełnoekranowy."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Kliknij, aby zrestartować aplikację i zyskać lepszą widoczność."</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_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Przeciągnij drugą aplikację, aby podzielić ekran"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Uruchomić ponownie dla lepszego wyglądu?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Możesz ponownie uruchomić aplikację, aby lepiej wyglądała na ekranie, ale istnieje ryzyko, że utracisz postępy i niezapisane zmiany"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anuluj"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacji"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
+ <string name="select_text" msgid="5139083974039906583">"Wybierz"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Zrzut ekranu"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
index b922e2d5a6ba..b598351b5127 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz w obrazie"</string>
<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_close" msgid="2955969519031223530">"Zamknij"</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>
+ <string name="pip_move" msgid="158770205886688553">"Przenieś"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Rozwiń"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Zwiń"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Naciśnij dwukrotnie "<annotation icon="home_icon">"EKRAN GŁÓWNY"</annotation>", aby wyświetlić ustawienia"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu funkcji Obraz w obrazie."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Przenieś w lewo"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Przenieś w prawo"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Przenieś w górę"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Przenieś w dół"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gotowe"</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 2efc5543dd87..c30b6f158bd2 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</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_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
+ <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
</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 cc4eb3c32c1f..2528ea9b8ebb 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<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_close" msgid="2955969519031223530">"Fechar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Abrir"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Fechar"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Pressione o botão "<annotation icon="home_icon">"HOME"</annotation>" duas vezes para acessar os controles"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu do picture-in-picture"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index c68a6934dead..00f4aa0d4b63 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Definições"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Aceder ao ecrã dividido"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu de ecrã no ecrã"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> está no modo de ecrã no ecrã"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor do ecrã dividido"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ecrã esquerdo inteiro"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% no ecrã esquerdo"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% no ecrã superior"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ecrã inferior inteiro"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Divisão à esquerda"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Divisão à direita"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Divisão na parte superior"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divisão na parte inferior"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilize o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Definições de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar balão"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não apresentar a conversa em balões"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse no chat através de balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"As novas conversas aparecem como ícones flutuantes ou balões. Toque para abrir o balão. Arraste para o mover."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar esta app e ficar em ecrã inteiro."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</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_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outra app para usar o ecrã dividido"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para uma melhor visualização?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Pode reiniciar a app para ficar com um melhor aspeto no seu ecrã, mas pode perder o seu progresso ou eventuais alterações não guardadas"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ícone da app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de ambiente de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de ecrã"</string>
+ <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
</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 c4ae78d89ba8..a678f581c272 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Ecrã no ecrã"</string>
<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_close" msgid="2955969519031223530">"Fechar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Expandir"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Reduzir"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Prima duas vezes "<annotation icon="home_icon">"PÁGINA INICIAL"</annotation>" para os controlos"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu de ecrã no ecrã."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 2efc5543dd87..c30b6f158bd2 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Configurações"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Dividir tela"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu do picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> está em picture-in-picture"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bolha"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gerenciar"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão dispensado."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</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_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
+ <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
index cc4eb3c32c1f..2528ea9b8ebb 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<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_close" msgid="2955969519031223530">"Fechar"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mover"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Abrir"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Fechar"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Pressione o botão "<annotation icon="home_icon">"HOME"</annotation>" duas vezes para acessar os controles"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu do picture-in-picture"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover para a esquerda"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mover para a direita"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mover para cima"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mover para baixo"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Concluído"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 804d34f980ff..4cfb1fa953dd 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -17,25 +17,28 @@
<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">"Închideți"</string>
- <string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string>
+ <string name="pip_phone_close" msgid="5783752637260411309">"Închide"</string>
+ <string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meniu picture-in-picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string>
- <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>
- <string name="pip_play" msgid="3496151081459417097">"Redați"</string>
- <string name="pip_pause" msgid="690688849510295232">"Întrerupeți"</string>
- <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="pip_notification_message" msgid="8854051911700302620">"Dacă nu vrei ca <xliff:g id="NAME">%s</xliff:g> să folosească această funcție, atinge pentru a deschide setările și dezactiveaz-o."</string>
+ <string name="pip_play" msgid="3496151081459417097">"Redă"</string>
+ <string name="pip_pause" msgid="690688849510295232">"Întrerupe"</string>
+ <string name="pip_skip_to_next" msgid="8403429188794867653">"Treci la următorul"</string>
+ <string name="pip_skip_to_prev" msgid="7172158111196394092">"Treci la cel anterior"</string>
+ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionează"</string>
+ <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stochează"</string>
+ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Separator pentru ecranul împărțit"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Partea stângă pe ecran complet"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Partea stângă: 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string>
@@ -46,39 +49,65 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Partea de sus: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Partea de jos pe ecran complet"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Împarte în stânga"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Împarte în dreapta"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Împarte în sus"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Împarte în jos"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Folosirea modului cu o mână"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisați în sus din partea de jos a ecranului sau atingeți oriunde deasupra ferestrei aplicației"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activați modul cu o mână"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Părăsiți modul cu o mână"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisează în sus din partea de jos a ecranului sau atinge oriunde deasupra ferestrei aplicației"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activează modul cu o mână"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Ieși din modul cu o mână"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Setări pentru baloanele <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Suplimentar"</string>
- <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adăugați înapoi în stivă"</string>
+ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adaugă înapoi în stivă"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g>"</string>
- <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mutați în stânga sus"</string>
- <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mutați în dreapta sus"</string>
- <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mutați în stânga jos"</string>
- <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mutați în dreapta jos"</string>
+ <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Mută în stânga sus"</string>
+ <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mută în dreapta sus"</string>
+ <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mută în stânga jos"</string>
+ <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
- <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închideți balonul"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișați conversația în balon"</string>
+ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
+ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atingeți pentru a deschide balonul. Trageți pentru a-l muta."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlați oricând baloanele"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atingeți Gestionați pentru a dezactiva baloanele din această aplicație"</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlează oricând baloanele"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Atinge Gestionează pentru a dezactiva baloanele din această aplicație"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nu există baloane recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Baloanele recente și baloanele respinse vor apărea aici"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
- <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</string>
+ <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</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="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="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ai probleme cu camera foto?\nAtinge pentru a reîncadra"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string>
+ <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Repornești pentru o vizualizare mai bună?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Poți să repornești aplicația ca să arate mai bine pe ecran, dar este posibil să pierzi progresul sau modificările nesalvate"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Pictograma aplicației"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
+ <string name="select_text" msgid="5139083974039906583">"Selectează"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Captură de ecran"</string>
+ <string name="close_text" msgid="4986518933445178928">"Închide"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index 86a30f49df15..c3226ff28934 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-picture"</string>
<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_close" msgid="2955969519031223530">"Închide"</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>
+ <string name="pip_move" msgid="158770205886688553">"Mută"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Extinde"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Restrânge"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Apasă de 2 ori "<annotation icon="home_icon">"ECRANUL DE PORNIRE"</annotation>" pentru comenzi"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meniu picture-in-picture."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mută la stânga"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mută la dreapta"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mută în sus"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Mută în jos"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Gata"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 95bf1cf11435..741a5d141ccb 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Настройки"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Включить разделение экрана"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинке\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> находится в режиме \"Картинка в картинке\""</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Чтобы отключить эту функцию для приложения \"<xliff:g id="NAME">%s</xliff:g>\", перейдите в настройки."</string>
<string name="pip_play" msgid="3496151081459417097">"Воспроизвести"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Разделитель экрана"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Приложение слева"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Приложение справа"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Приложение сверху"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Приложение снизу"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Использование режима управления одной рукой"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чтобы выйти, проведите по экрану снизу вверх или коснитесь области за пределами приложения."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запустить режим управления одной рукой"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перенести в правый нижний угол"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Скрыть всплывающий чат"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Нажмите, чтобы перезапустить приложение и перейти в полноэкранный режим."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Нажмите, чтобы перезапустить приложение и настроить удобный для просмотра вид"</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_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перезапустить приложение?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Вы можете перезапустить приложение, чтобы оно лучше смотрелось на экране. При этом ваш прогресс или несохраненные изменения могут быть утеряны."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отмена"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Значок приложения"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
+ <string name="select_text" msgid="5139083974039906583">"Выбрать"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+ <string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
index 08623e1e69c5..c8fb47913ec9 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картинка в картинке"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string>
- <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Закрыть"</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>
+ <string name="pip_move" msgid="158770205886688553">"Переместить"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Развернуть"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Свернуть"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Параметры: дважды нажмите "<annotation icon="home_icon">"кнопку главного экрана"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню \"Картинка в картинке\"."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Переместить влево"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Переместить вправо"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Переместить вверх"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Переместить вниз"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 23dd65ad7b31..680f1388247a 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"සැකසීම්"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"බෙදුම් තිරයට ඇතුළු වන්න"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"මෙනුව"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"පින්තූරය තුළ පින්තූරය මෙනුව"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> පින්තූරය-තුළ-පින්තූරය තුළ වේ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ඔබට <xliff:g id="NAME">%s</xliff:g> මෙම විශේෂාංගය භාවිත කිරීමට අවශ්‍ය නැති නම්, සැකසීම් විවෘත කිරීමට තට්ටු කර එය ක්‍රියාවිරහිත කරන්න."</string>
<string name="pip_play" msgid="3496151081459417097">"ධාවනය කරන්න"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්‍රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
<string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
+ <string name="divider_title" msgid="5482989479865361192">"බෙදුම්-තිර වෙන්කරණය"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"වම බෙදන්න"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"දකුණ බෙදන්න"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ඉහළ බෙදන්න"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"පහළ බෙදන්න"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"තනි-අත් ප්‍රකාරය භාවිත කරමින්"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"තනි අත් ප්‍රකාරය ආරම්භ කරන්න"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"පහළ දකුණට ගෙන යන්න"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> සැකසීම්"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"බුබුලු ඉවත ලන්න"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"මෙම යෙදුම යළි ඇරඹීමට සහ පූර්ණ තිරයට යාමට තට්ටු කරන්න."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"වඩා හොඳ දසුනක් ලබා ගැනීම සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න."</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_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"වඩා හොඳ දසුනක් සඳහා යළි අරඹන්න ද?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ඔබට එය ඔබේ තිරයෙහි වඩා හොඳින් පෙනෙන පරිදි යෙදුම යළි ඇරඹිය හැකි නමුත්, ඔබට ඔබේ ප්‍රගතිය හෝ නොසුරකින ලද වෙනස්කම් අහිමි විය හැක"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"අවලංගු කරන්න"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
+ <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"යෙදුම් නිරූපකය"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්‍රකාරය"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"තව"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
+ <string name="select_text" msgid="5139083974039906583">"තෝරන්න"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"තිර රුව"</string>
+ <string name="close_text" msgid="4986518933445178928">"වසන්න"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
index fbb0ebba0623..aa949ec75b3d 100644
--- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"පින්තූරය-තුළ-පින්තූරය"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string>
+ <string name="pip_close" msgid="2955969519031223530">"වසන්න"</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>
+ <string name="pip_move" msgid="158770205886688553">"ගෙන යන්න"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"දිග හරින්න"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"හකුළන්න"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"පාලන සඳහා "<annotation icon="home_icon">"නිවහන"</annotation>" දෙවරක් ඔබන්න"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"පින්තූරය තුළ පින්තූරය මෙනුව"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"වමට ගෙන යන්න"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"දකුණට ගෙන යන්න"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ඉහළට ගෙන යන්න"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"පහළට ගෙන යන්න"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"නිමයි"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index a231cacefb20..4b89ab132780 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavenia"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Prejsť na rozdelenú obrazovku"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Ponuka"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Ponuka obrazu v obraze"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v režime obraz v obraze"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Prehrať"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Rozdeľovač obrazovky"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ľavá – na celú obrazovku"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ľavá – 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Horná – 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolná – na celú obrazovku"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdeliť vľavo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdeliť vpravo"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdeliť hore"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdeliť dole"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používanie režimu jednej ruky"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustiť režim jednej ruky"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Presunúť doprava nadol"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavenia aplikácie <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavrieť bublinu"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovať konverzáciu ako bublinu"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Čet pomocou bublín"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nové konverzácie sa zobrazujú ako plávajúce ikony či bubliny. Bublinu otvoríte klepnutím. Premiestnite ju presunutím."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovať"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina bola zavretá."</string>
- <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="restart_button_description" msgid="6712141648865547958">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte."</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_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Rozdelenú obrazovku aktivujete presunutím ďalšie aplikácie"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Chcete ju reštartovať, aby mala lepší vzhľad?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikáciu môžete reštartovať, aby mala na obrazovke lepší vzhľad, ale môžete prísť o postup a všetky neuložené zmeny."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikácie"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim počítača"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Viac"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string>
+ <string name="select_text" msgid="5139083974039906583">"Vybrať"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Snímka obrazovky"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zavrieť"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
index 81cb0eafc759..d5562d519d4b 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Obraz v obraze"</string>
<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_close" msgid="2955969519031223530">"Zavrieť"</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>
+ <string name="pip_move" msgid="158770205886688553">"Presunúť"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Rozbaliť"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Zbaliť"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Ovládanie zobrazíte dvojitým stlačením "<annotation icon="home_icon">"DOMOV"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Ponuka obrazu v obraze."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Posunúť doľava"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Posunúť doprava"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Posunúť nahor"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Posunúť nadol"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Hotovo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index adeaae978eaa..6eb83edc8116 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Nastavitve"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Vklopi razdeljen zaslon"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meni"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Meni za sliko v sliki"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> je v načinu slika v sliki"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Predvajaj"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Razdelilnik zaslonov"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levi v celozaslonski način"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Zgornji 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Spodnji v celozaslonski način"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Delitev levo"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Delitev desno"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Delitev zgoraj"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Delitev spodaj"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Uporaba enoročnega načina"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Zagon enoročnega načina"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premakni spodaj desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavitve za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Opusti oblaček"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Pogovora ne prikaži v oblačku"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klepet z oblački"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi pogovori so prikazani kot lebdeče ikone ali oblački. Če želite odpreti oblaček, se ga dotaknite. Če ga želite premakniti, ga povlecite."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Mehurček"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblaček je bil opuščen."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Dotaknite se za vnovični zagon te aplikacije in preklop v celozaslonski način."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</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_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite znova zagnati za boljši pregled?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Če znova zaženete aplikacijo, bo prikaz na zaslonu boljši, vendar lahko izgubite napredek ali neshranjene spremembe."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Prekliči"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Več"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
+ <string name="select_text" msgid="5139083974039906583">"Izberi"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Posnetek zaslona"</string>
+ <string name="close_text" msgid="4986518933445178928">"Zapri"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
index 060aaa0ce647..a37375e1ae9c 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika v sliki"</string>
<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_close" msgid="2955969519031223530">"Zapri"</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>
+ <string name="pip_move" msgid="158770205886688553">"Premakni"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Razširi"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Strni"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Za kontrolnike dvakrat pritisnite gumb za "<annotation icon="home_icon">"ZAČETNI ZASLON"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Meni za sliko v sliki"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Premakni levo"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Premakni desno"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Premakni navzgor"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Premakni navzdol"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Končano"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 2839b4bae7e4..e804c523346b 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Cilësimet"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Hyr në ekranin e ndarë"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyja"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyja e \"Figurës brenda figurës\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> është në figurë brenda figurës"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Luaj"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Ndarësi i ekranit të ndarë"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ekrani i plotë majtas"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Majtas 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Lart 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ekrani i plotë poshtë"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Ndaj majtas"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Ndaj djathtas"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Ndaj lart"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ndaj në fund"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Po përdor modalitetin e përdorimit me një dorë"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Modaliteti i përdorimit me një dorë"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Lëvize poshtë djathtas"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cilësimet e <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Hiqe flluskën"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Mos e vendos bisedën në flluskë"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bisedo duke përdorur flluskat"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Bisedat e reja shfaqen si ikona pluskuese ose flluska. Trokit për të hapur flluskën. Zvarrit për ta zhvendosur."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Flluskë"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Menaxho"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Flluska u hoq."</string>
- <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="restart_button_description" msgid="6712141648865547958">"Trokit për të rifilluar këtë aplikacion për një pamje më të mirë."</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_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Rinis për një pamje më të mirë?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund të rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund të humbësh progresin ose çdo ndryshim të paruajtur"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
+ <string name="select_text" msgid="5139083974039906583">"Zgjidh"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Pamja e ekranit"</string>
+ <string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
index 9bfdb6a3edd8..3fbaaac2d3a2 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Figurë brenda figurës"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Mbyll"</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>
+ <string name="pip_move" msgid="158770205886688553">"Lëviz"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Zgjero"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Palos"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Trokit dy herë te "<annotation icon="home_icon">"KREU"</annotation>" për kontrollet"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menyja e \"Figurës brenda figurës\"."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Lëviz majtas"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Lëviz djathtas"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Lëviz lart"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Lëviz poshtë"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"U krye"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 9db6b7c63610..9aafa8705a0b 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Подешавања"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Уђи на подељени екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Мени"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Мени слике у слици."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> је слика у слици"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Ако не желите да <xliff:g id="NAME">%s</xliff:g> користи ову функцију, додирните да бисте отворили подешавања и искључили је."</string>
<string name="pip_play" msgid="3496151081459417097">"Пусти"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Разделник подељеног екрана"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Поделите лево"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Поделите десно"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Поделите у врху"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Поделите у дну"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Коришћење режима једном руком"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Покрените режим једном руком"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести доле десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Подешавања за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Одбаци облачић"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Додирните да бисте рестартовали апликацију и прешли у режим целог екрана."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Додирните да бисте рестартовали ову апликацију ради бољег приказа."</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_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Икона апликације"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Још"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
+ <string name="select_text" msgid="5139083974039906583">"Изаберите"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Снимак екрана"</string>
+ <string name="close_text" msgid="4986518933445178928">"Затворите"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
index 6bc5c87bab48..34950027772b 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Слика у слици"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Затвори"</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>
+ <string name="pip_move" msgid="158770205886688553">"Премести"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Прошири"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Скупи"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Двапут притисните "<annotation icon="home_icon">" HOME "</annotation>" за контроле"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Мени Слика у слици."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Померите налево"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Померите надесно"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Померите нагоре"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Померите надоле"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index f6bd55423cdc..6316d52b57d0 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Inställningar"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Starta delad skärm"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meny"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Bild-i-bild-meny"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> visas i bild-i-bild"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Spela upp"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Avdelare för delad skärm"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Helskärm på vänster skärm"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vänster 70 %"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Övre 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Helskärm på nedre skärm"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Till vänster på delad skärm"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Till höger på delad skärm"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Upptill på delad skärm"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Nedtill på delad skärm"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Använda enhandsläge"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Starta enhandsläge"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytta längst ned till höger"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Inställningar för <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Stäng bubbla"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Visa inte konversationen i bubblor"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatta med bubblor"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nya konversationer visas som flytande ikoner, så kallade bubblor. Tryck på bubblan om du vill öppna den. Dra den om du vill flytta den."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbla"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Hantera"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubblan ignorerades."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Tryck för att starta om appen i helskärmsläge."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Tryck för att starta om appen och få en bättre vy."</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_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra till en annan app för läget Delad skärm"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vill du starta om för en bättre vy?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan starta om appen så den passar bättre på skärmen men du kan förlora dina framsteg och eventuella osparade ändringar."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
+ <string name="select_text" msgid="5139083974039906583">"Välj"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skärmbild"</string>
+ <string name="close_text" msgid="4986518933445178928">"Stäng"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
index b3465ab1db85..7116ac162fbd 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Bild-i-bild"</string>
<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_close" msgid="2955969519031223530">"Stäng"</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>
+ <string name="pip_move" msgid="158770205886688553">"Flytta"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Utöka"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Komprimera"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Tryck snabbt två gånger på "<annotation icon="home_icon">"HEM"</annotation>" för inställningar"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Bild-i-bild-meny."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Flytta åt vänster"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Flytta åt höger"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Flytta uppåt"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Flytta nedåt"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Klar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index f6e558527ee5..46bd638a9393 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Mipangilio"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Weka skrini iliyogawanywa"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menyu ya kipengele cha Kupachika Picha ndani ya Picha nyingine."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> iko katika hali ya picha ndani ya picha nyingine"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Cheza"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Kitenganishi cha kugawa skrini"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrini nzima ya kushoto"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kushoto 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Juu 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrini nzima ya chini"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Gawanya sehemu ya kushoto"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Gawanya sehemu ya kulia"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Gawanya sehemu ya juu"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gawanya sehemu ya chini"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Kutumia hali ya kutumia kwa mkono mmoja"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Anzisha hali ya kutumia kwa mkono mmoja"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sogeza chini kulia"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mipangilio ya <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ondoa kiputo"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Usiweke viputo kwenye mazungumzo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Piga gumzo ukitumia viputo"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Mazungumzo mapya huonekena kama aikoni au viputo vinavyoelea. Gusa ili ufungue kiputo. Buruta ili ukisogeze."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Kiputo"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Dhibiti"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Umeondoa kiputo."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Gusa ili uzime na uwashe programu hii, kisha nenda kwenye skrini nzima."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora."</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_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Buruta ndani programu nyingine ili utumie hali ya skrini iliyogawanywa"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Ungependa kuzima kisha uwashe ili upate mwonekano bora zaidi?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Unaweza kuzima kisha uwashe programu yako ili ifanye kazi vizuri zaidi kwenye skrini yako, lakini unaweza kupoteza maendeleo yako au mabadiliko yoyote ambayo hayajahifadhiwa"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ghairi"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Aikoni ya Programu"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
+ <string name="select_text" msgid="5139083974039906583">"Chagua"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Picha ya skrini"</string>
+ <string name="close_text" msgid="4986518933445178928">"Funga"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
index baff49ed821a..1e9406f01433 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pachika Picha Ndani ya Picha Nyingine"</string>
<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_close" msgid="2955969519031223530">"Funga"</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>
+ <string name="pip_move" msgid="158770205886688553">"Hamisha"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Panua"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Kunja"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Bonyeza mara mbili kitufe cha "<annotation icon="home_icon">" UKURASA WA KWANZA "</annotation>" kupata vidhibiti"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menyu ya kipengele cha kupachika picha ndani ya picha nyingine."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sogeza kushoto"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sogeza kulia"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Sogeza juu"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Sogeza chini"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Imemaliza"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index d8334adfe5ef..10f040b0c682 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"அமைப்புகள்"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"திரைப் பிரிப்பு பயன்முறைக்குச் செல்"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"மெனு"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"பிக்ச்சர்-இன்-பிக்ச்சர் மெனு"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> தற்போது பிக்ச்சர்-இன்-பிக்ச்சரில் உள்ளது"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> இந்த அம்சத்தைப் பயன்படுத்த வேண்டாம் என நினைத்தால் இங்கு தட்டி அமைப்புகளைத் திறந்து இதை முடக்கவும்."</string>
<string name="pip_play" msgid="3496151081459417097">"இயக்கு"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
<string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
+ <string name="divider_title" msgid="5482989479865361192">"திரைப் பிரிப்பான்"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"இடதுபுறமாகப் பிரிக்கும்"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"வலதுபுறமாகப் பிரிக்கும்"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"மேற்புறமாகப் பிரிக்கும்"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"கீழ்புறமாகப் பிரிக்கும்"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே ஏதேனும் ஓர் இடத்தில் தட்டவும்"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ஒற்றைக் கைப் பயன்முறையைத் தொடங்கும்"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"கீழே வலதுபுறமாக நகர்த்து"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> அமைப்புகள்"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"குமிழை அகற்று"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கலாம், முழுத்திரையில் பார்க்கலாம்."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</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_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"சரியான விதத்தில் காட்டப்பட மீண்டும் தொடங்கவா?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ஆப்ஸை மீண்டும் தொடங்குவதன் மூலம் திரையில் அது சரியான விதத்தில் தோற்றமளிக்கும். ஆனால் செயல்நிலையையோ சேமிக்கப்படாமல் இருக்கும் மாற்றங்களையோ நீங்கள் இழக்கக்கூடும்."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ரத்துசெய்"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ஆப்ஸ் ஐகான்"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
+ <string name="select_text" msgid="5139083974039906583">"தேர்ந்தெடுக்கும்"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ஸ்கிரீன்ஷாட்"</string>
+ <string name="close_text" msgid="4986518933445178928">"மூடும்"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
index 4439e299c919..ef1bcf913eaf 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"பிக்ச்சர்-இன்-பிக்ச்சர்"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string>
+ <string name="pip_close" msgid="2955969519031223530">"மூடுக"</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>
+ <string name="pip_move" msgid="158770205886688553">"நகர்த்து"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"விரி"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"சுருக்கு"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"கட்டுப்பாடுகளுக்கு "<annotation icon="home_icon">"முகப்பு"</annotation>" பட்டனை இருமுறை அழுத்துக"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"பிக்ச்சர்-இன்-பிக்ச்சர் மெனு."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"இடப்புறம் நகர்த்து"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"வலப்புறம் நகர்த்து"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"மேலே நகர்த்து"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"கீழே நகர்த்து"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"முடிந்தது"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 733075550007..512bd528dd65 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -22,20 +22,23 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"సెట్టింగ్‌లు"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"స్ప్లిట్ స్క్రీన్‌ను ఎంటర్ చేయండి"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"మెనూ"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"పిక్చర్-ఇన్-పిక్చర్ మెనూ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> చిత్రంలో చిత్రం రూపంలో ఉంది"</string>
<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_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_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్‌ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
+ <string name="divider_title" msgid="5482989479865361192">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"ఎడమ వైపున్న భాగంలో విభజించండి"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"కుడి వైపున్న భాగంలో విభజించండి"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"ఎగువ భాగంలో విభజించండి"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"దిగువ భాగంలో విభజించండి"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్‌ను ఉపయోగించడం"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్‌ను ప్రారంభిస్తుంది"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"దిగవు కుడివైపునకు జరుపు"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> సెట్టింగ్‌లు"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"బబుల్‌ను విస్మరించు"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"ఈ యాప్‌ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేసి, ఆపై పూర్తి స్క్రీన్‌లోకి వెళ్లండి."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"మెరుగైన వీక్షణ కోసం ఈ యాప్‌ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి."</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_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"మెరుగైన వీక్షణ కోసం రీస్టార్ట్ చేయాలా?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"మీరు యాప్‌ని రీస్టార్ట్ చేయవచ్చు, తద్వారా ఇది మీ స్క్రీన్‌పై మెరుగ్గా కనిపిస్తుంది, కానీ మీరు మీ ప్రోగ్రెస్‌ను గానీ లేదా సేవ్ చేయని ఏవైనా మార్పులను గానీ కోల్పోవచ్చు"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"రద్దు చేయండి"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
+ <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"యాప్ చిహ్నం"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"డెస్క్‌టాప్ మోడ్"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
+ <string name="select_text" msgid="5139083974039906583">"ఎంచుకోండి"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"స్క్రీన్‌షాట్"</string>
+ <string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
index 35579346615f..d9237dff6dd8 100644
--- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string>
- <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string>
+ <string name="pip_close" msgid="2955969519031223530">"మూసివేయండి"</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>
+ <string name="pip_move" msgid="158770205886688553">"తరలించండి"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"విస్తరించండి"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"కుదించండి"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"కంట్రోల్స్ కోసం "<annotation icon="home_icon">"HOME"</annotation>" బటన్ రెండుసార్లు నొక్కండి"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"పిక్చర్-ఇన్-పిక్చర్ మెనూ."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ఎడమ వైపుగా జరపండి"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"కుడి వైపుగా జరపండి"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"పైకి జరపండి"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"కిందికి జరపండి"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"పూర్తయింది"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
index 14e89f8b08df..376cc4faca6f 100644
--- a/libs/WindowManager/Shell/res/values-television/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -21,4 +21,7 @@
<!-- Padding between PIP and keep clear areas that caused it to move. -->
<dimen name="pip_keep_clear_area_padding">16dp</dimen>
+
+ <!-- The corner radius for PiP window. -->
+ <dimen name="pip_corner_radius">0dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index cfee8ea3242e..e53386956785 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"การตั้งค่า"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"เข้าสู่โหมดแบ่งหน้าจอ"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"เมนู"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"เมนูการแสดงภาพซ้อนภาพ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ใช้การแสดงภาพซ้อนภาพ"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"หากคุณไม่ต้องการให้ <xliff:g id="NAME">%s</xliff:g> ใช้ฟีเจอร์นี้ ให้แตะเพื่อเปิดการตั้งค่าแล้วปิดฟีเจอร์"</string>
<string name="pip_play" msgid="3496151081459417097">"เล่น"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
<string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
+ <string name="divider_title" msgid="5482989479865361192">"เส้นแยกหน้าจอ"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"แยกไปทางซ้าย"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"แยกไปทางขวา"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"แยกไปด้านบน"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"แยกไปด้านล่าง"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"การใช้โหมดมือเดียว"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"เริ่มโหมดมือเดียว"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขาวล่าง"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"การตั้งค่า <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ปิดบับเบิล"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"แตะเพื่อรีสตาร์ทแอปนี้และแสดงแบบเต็มหน้าจอ"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</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_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"รีสตาร์ทเพื่อรับมุมมองที่ดียิ่งขึ้นใช่ไหม"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"คุณรีสตาร์ทแอปเพื่อรับมุมมองที่ดียิ่งขึ้นบนหน้าจอได้ แต่ความคืบหน้าและการเปลี่ยนแปลงใดๆ ที่ไม่ได้บันทึกอาจหายไป"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ยกเลิก"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ไอคอนแอป"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"โหมดเดสก์ท็อป"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string>
+ <string name="select_text" msgid="5139083974039906583">"เลือก"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"ภาพหน้าจอ"</string>
+ <string name="close_text" msgid="4986518933445178928">"ปิด"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
index 0a07d157ec6f..47a6bd1be812 100644
--- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"การแสดงภาพซ้อนภาพ"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string>
- <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"ปิด"</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>
+ <string name="pip_move" msgid="158770205886688553">"ย้าย"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"ขยาย"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"ยุบ"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"กดปุ่ม "<annotation icon="home_icon">" หน้าแรก "</annotation>" สองครั้งเพื่อเปิดการควบคุม"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"เมนูการแสดงภาพซ้อนภาพ"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ย้ายไปทางซ้าย"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ย้ายไปทางขวา"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ย้ายขึ้น"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"ย้ายลง"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"เสร็จ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index eed624dd5069..426e83a560cd 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Mga Setting"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Pumasok sa split screen"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu ng Picture-in-Picture"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Nasa picture-in-picture ang <xliff:g id="NAME">%s</xliff:g>"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"I-play"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Divider ng split-screen"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"I-full screen ang nasa kaliwa"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Gawing 70% ang nasa kaliwa"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gawing 30% ang nasa itaas"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"I-full screen ang nasa ibaba"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Hatiin sa kaliwa"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Hatiin sa kanan"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Hatiin sa itaas"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hatiin sa ilalim"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Paggamit ng one-hand mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Simulan ang one-hand mode"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ilipat sa kanan sa ibaba"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mga setting ng <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"I-dismiss ang bubble"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Huwag ipakita sa bubble ang mga pag-uusap"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Mag-chat gamit ang bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Lumalabas bilang mga nakalutang na icon o bubble ang mga bagong pag-uusap. I-tap para buksan ang bubble. I-drag para ilipat ito."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Pamahalaan"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Na-dismiss na ang bubble."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"I-tap para i-restart ang app na ito at mag-full screen."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"I-tap para i-restart ang app na ito para sa mas magandang view."</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_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Mag-drag ng ibang app para sa split screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"I-restart para sa mas magandang hitsura?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puwede mong i-restart ang app para maging mas maganda ang itsura nito sa iyong screen, pero posibleng mawala ang pag-usad mo o anumang hindi na-save na pagbabago"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icon ng App"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+ <string name="select_text" msgid="5139083974039906583">"Piliin"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Isara"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
index 9a11a38fa492..2d890d4126b6 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<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_close" msgid="2955969519031223530">"Isara"</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>
+ <string name="pip_move" msgid="158770205886688553">"Ilipat"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"I-expand"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"I-collapse"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"I-double press ang "<annotation icon="home_icon">"HOME"</annotation>" para sa mga kontrol"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menu ng Picture-in-Picture."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Ilipat pakaliwa"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Ilipat pakanan"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Itaas"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Ibaba"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Tapos na"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 2b4a2d0550f0..969e4030be22 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Ayarlar"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Bölünmüş ekrana geç"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Pencere içinde pencere menüsü"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>, pencere içinde pencere özelliğini kullanıyor"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Oynat"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcı"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Solda tam ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Solda %70"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Üstte %30"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Altta tam ekran"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Sol tarafta böl"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Sağ tarafta böl"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Üst tarafta böl"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alt tarafta böl"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Tek el modunu kullanma"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Tek el modunu başlat"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sağ alta taşı"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baloncuğu kapat"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Görüşmeyi baloncuk olarak görüntüleme"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Baloncukları kullanarak sohbet edin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yeni görüşmeler kayan simgeler veya baloncuk olarak görünür. Açmak için baloncuğa dokunun. Baloncuğu taşımak için sürükleyin."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Baloncuk"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Yönet"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balon kapatıldı."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Bu uygulamayı yeniden başlatmak ve tam ekrana geçmek için dokunun."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun."</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_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Daha iyi bir görünüm için yeniden başlatılsın mı?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ekranınızda daha iyi görünmesi için uygulamayı yeniden başlatabilirsiniz, ancak ilerlemenizi ve kaydedilmemiş değişikliklerinizi kaybedebilirsiniz"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"İptal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Uygulama Simgesi"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
+ <string name="select_text" msgid="5139083974039906583">"Seç"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Ekran görüntüsü"</string>
+ <string name="close_text" msgid="4986518933445178928">"Kapat"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
index bf4bc6f1fff7..9e3e59b74c19 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pencere İçinde Pencere"</string>
<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_close" msgid="2955969519031223530">"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>
+ <string name="pip_move" msgid="158770205886688553">"Taşı"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Genişlet"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Daralt"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Kontroller için "<annotation icon="home_icon">"ANA EKRAN"</annotation>" düğmesine iki kez basın"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Pencere içinde pencere menüsü."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Sola taşı"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Sağa taşı"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Yukarı taşı"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Aşağı taşı"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Bitti"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 02e726fbc3bf..0b61d7a85d9e 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -15,20 +15,20 @@
limitations under the License.
-->
<resources>
- <!-- The dimensions to user for picture-in-picture action buttons. -->
- <dimen name="pip_menu_button_size">48dp</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>
- <integer name="pip_menu_fade_animation_duration">500</integer>
+ <!-- The dimensions to use for tv window menu action buttons. -->
+ <dimen name="tv_window_menu_button_size">48dp</dimen>
+ <dimen name="tv_window_menu_button_radius">20dp</dimen>
+ <dimen name="tv_window_menu_icon_size">20dp</dimen>
+ <dimen name="tv_window_menu_button_margin">4dp</dimen>
+ <integer name="tv_window_menu_fade_animation_duration">500</integer>
<!-- The pip menu front border corner radius is 2dp smaller than
the background corner radius to hide the background from
showing through. -->
<dimen name="pip_menu_border_corner_radius">4dp</dimen>
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
+ <dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
+ <dimen name="pip_menu_button_start_end_offset">30dp</dimen>
<!-- outer space minus border width -->
<dimen name="pip_menu_outer_space_frame">20dp</dimen>
@@ -41,8 +41,10 @@
<dimen name="pip_menu_edu_text_view_height">24dp</dimen>
<dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
<dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
- <integer name="pip_edu_text_show_duration_ms">10500</integer>
- <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
- <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
+ <integer name="pip_edu_text_scroll_times">2</integer>
+ <integer name="pip_edu_text_non_scroll_show_duration">10500</integer>
+ <integer name="pip_edu_text_start_scroll_delay">2000</integer>
+ <integer name="pip_edu_text_window_exit_animation_duration">1000</integer>
+ <integer name="pip_edu_text_view_exit_animation_duration">300</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index c3411a837c78..e265e2f0f54b 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Налаштування"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Розділити екран"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Меню \"Картинка в картинці\""</string>
<string name="pip_notification_title" msgid="1347104727641353453">"У додатку <xliff:g id="NAME">%s</xliff:g> є функція \"Картинка в картинці\""</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Щоб додаток <xliff:g id="NAME">%s</xliff:g> не використовував цю функцію, вимкніть її в налаштуваннях."</string>
<string name="pip_play" msgid="3496151081459417097">"Відтворити"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Розділювач екрана"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"Розділити зліва"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Розділити справа"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Розділити вгорі"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Розділити внизу"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Як користуватися режимом керування однією рукою"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Щоб вийти, проведіть пальцем по екрану знизу вгору або торкніться екрана над додатком"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Увімкнути режим керування однією рукою"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перемістити праворуч униз"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Налаштування параметра \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Закрити підказку"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Натисніть, щоб перезапустити додаток і перейти в повноекранний режим."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду."</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_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перезапустити для зручнішого перегляду?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ви можете перезапустити додаток, щоб покращити його вигляд на екрані, але ваші досягнення або незбережені зміни може бути втрачено"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасувати"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Значок додатка"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
+ <string name="select_text" msgid="5139083974039906583">"Вибрати"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Знімок екрана"</string>
+ <string name="close_text" msgid="4986518933445178928">"Закрити"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
index 7e9f54e68f54..5edb26977fe7 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Картинка в картинці"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Закрити"</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>
+ <string name="pip_move" msgid="158770205886688553">"Перемістити"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Розгорнути"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Згорнути"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Відкрити елементи керування: двічі натисніть "<annotation icon="home_icon">"HOME"</annotation></string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Меню \"картинка в картинці\""</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Перемістити ліворуч"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Перемістити праворуч"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Перемістити вгору"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Перемістити вниз"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Готово"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index a31c2be25643..f4373e6fb634 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"مینو"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینو"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> تصویر میں تصویر میں ہے"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"اگر آپ نہیں چاہتے ہیں کہ <xliff:g id="NAME">%s</xliff:g> اس خصوصیت کا استعمال کرے تو ترتیبات کھولنے کے لیے تھپتھپا کر اسے آف کرے۔"</string>
<string name="pip_play" msgid="3496151081459417097">"چلائیں"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
<string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
+ <string name="divider_title" msgid="5482989479865361192">"اسپلٹ اسکرین ڈیوائیڈر"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"دائیں طرف تقسیم کریں"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"بائیں طرف تقسیم کریں"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"اوپر کی طرف تقسیم کریں"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"نیچے کی طرف تقسیم کریں"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ایک ہاتھ کی وضع کا استعمال کرنا"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"باہر نکلنے کیلئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ایک ہاتھ کی وضع شروع کریں"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نیچے دائیں جانب لے جائیں"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ترتیبات"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"بلبلہ برخاست کریں"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"یہ ایپ دوبارہ شروع کرنے کے لیے تھپتھپائیں اور پوری اسکرین پر جائیں۔"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں۔"</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_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"بہتر منظر کے لیے ری سٹارٹ کریں؟"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"آپ ایپ کو ری سٹارٹ کر سکتے ہیں تاکہ یہ آپ کی اسکرین پر بہتر نظر آئے، تاہم آپ اپنی پیشرفت سے یا کسی غیر محفوظ شدہ تبدیلیوں سے محروم ہو سکتے ہیں"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"منسوخ کریں"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"ایپ کا آئیکن"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
+ <string name="select_text" msgid="5139083974039906583">"منتخب کریں"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"اسکرین شاٹ"</string>
+ <string name="close_text" msgid="4986518933445178928">"بند کریں"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"مینو بند کریں"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index c2ef69ff1488..42b9564ff549 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر میں تصویر"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
- <string name="pip_close" msgid="9135220303720555525">"‏PIP بند کریں"</string>
+ <string name="pip_close" msgid="2955969519031223530">"بند کریں"</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>
+ <string name="pip_move" msgid="158770205886688553">"منتقل کریں"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"پھیلائیں"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"سکیڑیں"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" کو دو بار دبائیں"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینو۔"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"دائیں منتقل کریں"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"بائیں منتقل کریں"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"اوپر منتقل کریں"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"نیچے منتقل کریں"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"ہو گیا"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 2e3222560dde..8c07814042e2 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Sozlamalar"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Ajratilgan ekranga kirish"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menyu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Tasvir ustida tasvir menyusi"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> tasvir ustida tasvir rejimida"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ilovasi uchun bu funksiyani sozlamalar orqali faolsizlantirish mumkin."</string>
<string name="pip_play" msgid="3496151081459417097">"Ijro"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Ekranni ikkiga ajratish chizigʻi"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Chapda to‘liq ekran"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Chapda 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Tepada 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pastda to‘liq ekran"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Chapga ajratish"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Oʻngga ajratish"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Yuqoriga ajratish"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pastga ajratish"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ixcham rejimdan foydalanish"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ixcham rejimni ishga tushirish"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Quyi oʻngga surish"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> sozlamalari"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bulutchani yopish"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Suhbatlar bulutchalar shaklida chiqmasin"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bulutchalar yordamida subhatlashish"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yangi xabarlar qalqib chiquvchi belgilar yoki bulutchalar kabi chiqadi. Xabarni ochish uchun bildirishnoma ustiga bosing. Xabarni qayta joylash uchun bildirishnomani suring."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Pufaklar"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Boshqarish"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulutcha yopildi."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Bu ilovani qaytadan ishga tushirish va butun ekranda ochish uchun bosing."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing."</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_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Yaxshi koʻrinishi uchun qayta ishga tushirilsinmi?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ilovani ekranda yaxshiroq koʻrinishi uchun qayta ishga tushirishingiz mumkin. Bunda jarayonlar yoki saqlanmagan oʻzgarishlar yoʻqolishi mumkin."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Bekor qilish"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Ilova belgisi"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop rejimi"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Yana"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string>
+ <string name="select_text" msgid="5139083974039906583">"Tanlash"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skrinshot"</string>
+ <string name="close_text" msgid="4986518933445178928">"Yopish"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
index 9ab95c80aa25..83fd8b4e5425 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Tasvir ustida tasvir"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string>
- <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string>
+ <string name="pip_close" msgid="2955969519031223530">"Yopish"</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>
+ <string name="pip_move" msgid="158770205886688553">"Boshqa joyga olish"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Yoyish"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Yopish"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Boshqaruv uchun "<annotation icon="home_icon">"ASOSIY"</annotation>" tugmani ikki marta bosing"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Tasvir ustida tasvir menyusi."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Chapga olish"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Oʻngga olish"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Tepaga olish"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Pastga olish"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Tayyor"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 8f3cffecc952..bbb363932d55 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Cài đặt"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Truy cập chế độ chia đôi màn hình"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Trình đơn hình trong hình."</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> đang ở chế độ ảnh trong ảnh"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Phát"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Bộ chia màn hình"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Toàn màn hình bên trái"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Trái 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Trên 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Toàn màn hình phía dưới"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Chia đôi màn hình về bên trái"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Chia đôi màn hình về bên phải"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Chia đôi màn hình lên trên cùng"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Chia đôi màn hình xuống dưới cùng"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cách dùng chế độ một tay"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bắt đầu chế độ một tay"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Chuyển tới dưới cùng bên phải"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cài đặt <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Đóng bong bóng"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Dừng sử dụng bong bóng cho cuộc trò chuyện"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Trò chuyện bằng bong bóng trò chuyện"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Các cuộc trò chuyện mới sẽ xuất hiện dưới dạng biểu tượng nổi hoặc bong bóng trò chuyện. Nhấn để mở bong bóng trò chuyện. Kéo để di chuyển bong bóng trò chuyện."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string>
- <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="restart_button_description" msgid="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</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_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Kéo vào một ứng dụng khác để chia đôi màn hình"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Khởi động lại để ứng dụng trông vừa vặn hơn?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Bạn có thể khởi động lại ứng dụng để ứng dụng nhìn đẹp hơn trên màn hình. Tuy nhiên, nếu làm vậy, bạn có thể mất tiến trình hoặc mọi thay đổi chưa lưu"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Huỷ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Biểu tượng ứng dụng"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
+ <string name="select_text" msgid="5139083974039906583">"Chọn"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Ảnh chụp màn hình"</string>
+ <string name="close_text" msgid="4986518933445178928">"Đóng"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
index 146376d3cab6..986690f0444c 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Hình trong hình"</string>
<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_close" msgid="2955969519031223530">"Đóng"</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>
+ <string name="pip_move" msgid="158770205886688553">"Di chuyển"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Mở rộng"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Thu gọn"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"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>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Trình đơn hình trong hình."</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Di chuyển sang trái"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Di chuyển sang phải"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Di chuyển lên"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Di chuyển xuống"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Xong"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 19a9d371e435..caca25a1a76c 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"设置"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"进入分屏模式"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"菜单"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"画中画菜单"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g>目前位于“画中画”中"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"如果您不想让“<xliff:g id="NAME">%s</xliff:g>”使用此功能,请点按以打开设置,然后关闭此功能。"</string>
<string name="pip_play" msgid="3496151081459417097">"播放"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
+ <string name="divider_title" msgid="5482989479865361192">"分屏分隔线"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"左分屏"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"右分屏"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"上分屏"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"下分屏"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用单手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"启动单手模式"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下角"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭对话泡。"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"点按即可重启此应用并进入全屏模式。"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"点按即可重启此应用,获得更好的视图体验。"</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_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"重启以改进外观?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可以重启应用,使其在屏幕上的显示效果更好,但您可能会丢失进度或任何未保存的更改"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+ <string name="handle_text" msgid="1766582106752184456">"处理"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"应用图标"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
+ <string name="select_text" msgid="5139083974039906583">"选择"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"屏幕截图"</string>
+ <string name="close_text" msgid="4986518933445178928">"关闭"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
</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 55407d2c699d..4da96e89f136 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"画中画"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string>
- <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string>
+ <string name="pip_close" msgid="2955969519031223530">"关闭"</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>
+ <string name="pip_move" msgid="158770205886688553">"移动"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"展开"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"收起"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"按两次"<annotation icon="home_icon">"主屏幕"</annotation>"按钮可查看相关控件"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"画中画菜单。"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"左移"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"右移"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"上移"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"下移"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</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 0c40e963f2e4..7a2d3485245c 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割螢幕"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在畫中畫模式"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"如果您不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string>
<string name="pip_play" msgid="3496151081459417097">"播放"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
+ <string name="divider_title" msgid="5482989479865361192">"分割螢幕分隔線"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"分割左側區域"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"開始單手模式"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移去右下角"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉小視窗氣泡"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"輕按即可重新開啟此應用程式並放大至全螢幕。"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗。"</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_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及您作出的任何變更"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返去"</string>
+ <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
+ <string name="select_text" msgid="5139083974039906583">"選取"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
+ <string name="close_text" msgid="4986518933445178928">"關閉"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
</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 15e278d8ecc2..ce850ef3c675 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"畫中畫"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string>
- <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string>
+ <string name="pip_close" msgid="2955969519031223530">"關閉"</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>
+ <string name="pip_move" msgid="158770205886688553">"移動"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"展開"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"收合"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"按兩下"<annotation icon="home_icon">" 主畫面按鈕"</annotation>"即可存取控制項"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"畫中畫選單。"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"向左移"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"向右移"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"向上移"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"向下移"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 8691352cf94a..b0ccd8a82905 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -22,6 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"設定"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"進入分割畫面"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"子母畫面選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在子母畫面中"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"如果你不想讓「<xliff:g id="NAME">%s</xliff:g>」使用這項功能,請輕觸開啟設定頁面,然後停用此功能。"</string>
<string name="pip_play" msgid="3496151081459417097">"播放"</string>
@@ -33,9 +34,11 @@
<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_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
+ <string name="divider_title" msgid="5482989479865361192">"分割畫面分隔線"</string>
<string name="accessibility_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>
@@ -46,6 +49,10 @@
<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_split_left" msgid="1713683765575562458">"分割左側區域"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"啟動單手模式"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下方"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"關閉對話框"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<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>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string>
- <string name="restart_button_description" msgid="5887656107651190519">"輕觸即可重新啟動這個應用程式並進入全螢幕模式。"</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"請輕觸並重新啟動此應用程式,取得更良好的觀看體驗。"</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_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+ <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
+ <string name="select_text" msgid="5139083974039906583">"選取"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
+ <string name="close_text" msgid="4986518933445178928">"關閉"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
</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 0b17b31d23d0..df870851e72b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"子母畫面"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string>
- <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string>
+ <string name="pip_close" msgid="2955969519031223530">"關閉"</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>
+ <string name="pip_move" msgid="158770205886688553">"移動"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"展開"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"收合"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"按兩下"<annotation icon="home_icon">"主畫面按鈕"</annotation>"即可存取控制選項"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"子母畫面選單。"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"向左移動"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"向右移動"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"向上移動"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"向下移動"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"完成"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 44ffbc6afa45..7787e774763e 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -19,9 +19,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<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_settings" msgid="5468987116750491918">"Amasethingi"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
+ <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Imenyu Yesithombe-Esithombeni"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
<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>
<string name="pip_play" msgid="3496151081459417097">"Dlala"</string>
@@ -33,9 +34,11 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
+ <string name="divider_title" msgid="5482989479865361192">"Isihlukanisi sokuhlukanisa isikrini"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Isikrini esigcwele esingakwesokunxele"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kwesokunxele ngo-70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string>
@@ -46,6 +49,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Okuphezulu okungu-30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ngaphansi kwesikrini esigcwele"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Hlukanisa ngakwesobunxele"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Hlukanisa ngakwesokudla"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Hlukanisa phezulu"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hlukanisa phansi"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ukusebenzisa imodi yesandla esisodwa"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Qalisa imodi yesandla esisodwa"</string>
@@ -61,6 +68,8 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Hambisa inkinobho ngakwesokudla"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> izilungiselelo"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cashisa ibhamuza"</string>
+ <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
+ <skip />
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ungayibhamuzi ingxoxo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xoxa usebenzisa amabhamuza"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Izingxoxo ezintsha zivela njengezithonjana ezintantayo, noma amabhamuza. Thepha ukuze uvule ibhamuza. Hudula ukuze ulihambise."</string>
@@ -72,13 +81,33 @@
<string name="notification_bubble_title" msgid="6082910224488253378">"Ibhamuza"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Phatha"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ibhamuza licashisiwe."</string>
- <string name="restart_button_description" msgid="5887656107651190519">"Thepha ukuze uqale kabusha lolu hlelo lokusebenza uphinde uye kusikrini esigcwele."</string>
+ <string name="restart_button_description" msgid="6712141648865547958">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono."</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_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string>
+ <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
+ <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Qala kabusha ukuze uthole ukubuka okungcono?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ungakwazi ukuqala kabusha i-app ukuze ibukeke kangcono esikrinini sakho, kodwa ungase ulahlekelwe ukuqhubeka kwakho nanoma yiziphi izinguquko ezingalondoloziwe"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
+ <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Isithonjana Se-app"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
+ <string name="select_text" msgid="5139083974039906583">"Khetha"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Isithombe-skrini"</string>
+ <string name="close_text" msgid="4986518933445178928">"Vala"</string>
+ <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
index dad8c8128222..34cc8f1f1647 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
@@ -19,10 +19,16 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Isithombe-esithombeni"</string>
<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_close" msgid="2955969519031223530">"Vala"</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>
+ <string name="pip_move" msgid="158770205886688553">"Hambisa"</string>
+ <string name="pip_expand" msgid="1051966011679297308">"Nweba"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Goqa"</string>
+ <string name="pip_edu_text" msgid="7930546669915337998">"Chofoza kabili "<annotation icon="home_icon">" IKHAYA"</annotation>" mayelana nezilawuli"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Imenyu yesithombe-esithombeni"</string>
+ <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Yisa kwesokunxele"</string>
+ <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Yisa kwesokudla"</string>
+ <string name="a11y_action_pip_move_up" msgid="98502616918621959">"Khuphula"</string>
+ <string name="a11y_action_pip_move_down" msgid="3858802832725159740">"Yehlisa"</string>
+ <string name="a11y_action_pip_move_done" msgid="1486845365134416210">"Kwenziwe"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index fa90fe36b545..e6933ca3fce6 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -15,14 +15,17 @@
~ 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_window_menu_icon_focused">#0E0E0F</color>
+ <color name="tv_window_menu_icon_unfocused">#F8F9FA</color>
+
+ <color name="tv_window_menu_icon_disabled">#80868B</color>
+ <color name="tv_window_menu_close_icon_bg_focused">#D93025</color>
+ <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color>
+ <color name="tv_window_menu_icon_bg_focused">#E8EAED</color>
+ <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
+
<color name="tv_pip_menu_focus_border">#E8EAED</color>
+ <color name="tv_pip_menu_dim_layer">#990E0E0F</color>
<color name="tv_pip_menu_background">#1E232C</color>
<color name="tv_pip_edu_text">#99D2E3FC</color>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 6399232919d2..9f6cf79dcbc4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -30,6 +30,9 @@
<!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
<string name="pip_menu_title">Menu</string>
+ <!-- accessibility Title of menu shown over picture-in-picture [CHAR LIMIT=NONE] -->
+ <string name="pip_menu_accessibility_title">Picture-in-Picture Menu</string>
+
<!-- PiP BTW notification title. [CHAR LIMIT=50] -->
<string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string>
@@ -75,8 +78,10 @@
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
<string name="activity_launch_on_secondary_display_failed_text">App does not support launch on secondary displays.</string>
- <!-- Accessibility label for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] -->
+ <!-- Accessibility label and window tile for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] -->
<string name="accessibility_divider">Split-screen divider</string>
+ <!-- Accessibility window title for the split-screen divider window [CHAR LIMIT=NONE] -->
+ <string name="divider_title">Split-screen divider</string>
<!-- Accessibility action for moving docked stack divider to make the left screen full screen [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_left_full">Left full screen</string>
@@ -141,6 +146,8 @@
<string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
<string name="bubble_dismiss_text">Dismiss bubble</string>
+ <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]-->
+ <string name="bubbles_dont_bubble">Don\u2019t bubble</string>
<!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
<string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
<!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml
index 2b7a13eac6ca..8f806cf56c9b 100644
--- a/libs/WindowManager/Shell/res/values/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values/strings_tv.xml
@@ -42,8 +42,8 @@
<!-- 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>
+ <string name="pip_edu_text">Double press <annotation icon="home_icon">HOME</annotation> for
+ controls</string>
<!-- Accessibility announcement when opening the PiP menu. [CHAR LIMIT=NONE] -->
<string name="a11y_pip_menu_entered">Picture-in-Picture menu.</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index b085b73d78ce..34bf9e0dd98f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -76,6 +76,7 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
+ leash.setUnreleasedWarningCallSite("RootDisplayAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreasInfo.put(displayId, displayAreaInfo);
mLeashes.put(displayId, leash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index ca977ed2cb94..544d75739547 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -122,6 +122,8 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
+ " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
}
+ leash.setUnreleasedWarningCallSite(
+ "RootTaskDisplayAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreasInfo.put(displayId, displayAreaInfo);
mLeashes.put(displayId, leash);
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 97a9fede22d5..b6fd0bbafc71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -42,6 +42,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
+import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -427,9 +428,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@Override
- public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
- mStartingWindow.addStartingWindow(info, appToken);
+ mStartingWindow.addStartingWindow(info);
}
}
@@ -463,6 +464,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (leash != null) {
+ leash.setUnreleasedWarningCallSite("ShellTaskOrganizer.onTaskAppeared");
+ }
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
}
@@ -489,7 +493,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
* Take a screenshot of a task.
*/
public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
+ Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
if (info == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING
new file mode 100644
index 000000000000..8dd1369ecbb2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "ironwood-postsubmit": [
+ {
+ "name": "WMShellFlickerTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
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 95a89b1d23ff..7a6aec718006 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -16,8 +16,6 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -30,27 +28,20 @@ import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Binder;
-import android.util.CloseGuard;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewTreeObserver;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.SyncTransactionQueue;
-import java.io.PrintWriter;
import java.util.concurrent.Executor;
/**
- * View that can display a task.
+ * A {@link SurfaceView} that can display a task. This is a concrete implementation for
+ * {@link TaskViewBase} which interacts {@link TaskViewTaskController}.
*/
public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
- ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
-
+ ViewTreeObserver.OnComputeInternalInsetsListener, TaskViewBase {
/** Callback for listening task state. */
public interface Listener {
/**
@@ -75,66 +66,33 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
default void onBackPressedOnTaskRoot(int taskId) {}
}
- private final CloseGuard mGuard = new CloseGuard();
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final Executor mShellExecutor;
- private final SyncTransactionQueue mSyncQueue;
- private final TaskViewTransitions mTaskViewTransitions;
-
- protected ActivityManager.RunningTaskInfo mTaskInfo;
- private WindowContainerToken mTaskToken;
- private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
- private boolean mSurfaceCreated;
- private boolean mIsInitialized;
- private boolean mNotifiedForInitialized;
- private Listener mListener;
- private Executor mListenerExecutor;
- private Region mObscuredTouchRegion;
-
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
+ private final TaskViewTaskController mTaskViewTaskController;
+ private Region mObscuredTouchRegion;
- public TaskView(Context context, ShellTaskOrganizer organizer,
- TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+ public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
-
- mTaskOrganizer = organizer;
- mShellExecutor = organizer.getExecutor();
- mSyncQueue = syncQueue;
- mTaskViewTransitions = taskViewTransitions;
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.addTaskView(this);
- }
- setUseAlpha();
+ mTaskViewTaskController = taskViewTaskController;
+ // TODO(b/266736992): Think about a better way to set the TaskViewBase on the
+ // TaskViewTaskController and vice-versa
+ mTaskViewTaskController.setTaskViewBase(this);
getHolder().addCallback(this);
- mGuard.open("release");
- }
-
- /**
- * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
- */
- public boolean isInitialized() {
- return mIsInitialized;
- }
-
- /** Until all users are converted, we may have mixed-use (eg. Car). */
- private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
- * Only one listener may be set on the view, throws an exception otherwise.
+ * Launch a new activity.
+ *
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
*/
- public void setListener(@NonNull Executor executor, Listener listener) {
- if (mListener != null) {
- throw new IllegalStateException(
- "Trying to set a listener when one has already been set");
- }
- mListener = listener;
- mListenerExecutor = executor;
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+ mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds);
}
/**
@@ -149,61 +107,47 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
*/
public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
@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;
+ mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ onLocationChanged();
+ if (taskInfo.taskDescription != null) {
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
}
- try {
- service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo.taskDescription != null) {
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
}
}
/**
- * Launch a new activity.
- *
- * @param pendingIntent Intent used to launch an activity.
- * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
- * @param options options for the activity.
- * @param launchBounds the bounds (window size and position) that the activity should be
- * launched in, in pixels and in screen coordinates.
+ * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
*/
- 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 */,
- options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ public boolean isInitialized() {
+ return mTaskViewTaskController.isInitialized();
+ }
+
+ @Override
+ public Rect getCurrentBoundsOnScreen() {
+ getBoundsOnScreen(mTmpRect);
+ return mTmpRect;
}
- private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
- final Binder launchCookie = new Binder();
- mShellExecutor.execute(() -> {
- mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
- });
- options.setLaunchBounds(launchBounds);
- options.setLaunchCookie(launchCookie);
- options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- options.setRemoveWithTaskOrganizer(true);
+ @Override
+ public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
+ setResizeBackgroundColor(t, bgColor);
+ }
+
+ /**
+ * Only one listener may be set on the view, throws an exception otherwise.
+ */
+ public void setListener(@NonNull Executor executor, TaskView.Listener listener) {
+ mTaskViewTaskController.setListener(executor, listener);
}
/**
@@ -228,251 +172,38 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
* 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();
- updateWindowBounds(wct);
- mSyncQueue.queue(wct);
- }
-
- private void updateWindowBounds(WindowContainerTransaction wct) {
getBoundsOnScreen(mTmpRect);
- wct.setBounds(mTaskToken, mTmpRect);
+ mTaskViewTaskController.setWindowBounds(mTmpRect);
}
/**
* Release this container if it is initialized.
*/
public void release() {
- performRelease();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mGuard != null) {
- mGuard.warnIfOpen();
- performRelease();
- }
- } finally {
- super.finalize();
- }
- }
-
- private void performRelease() {
getHolder().removeCallback(this);
- if (mTaskViewTransitions != null) {
- mTaskViewTransitions.removeTaskView(this);
- }
- mShellExecutor.execute(() -> {
- mTaskOrganizer.removeListener(this);
- resetTaskInfo();
- });
- mGuard.close();
- mIsInitialized = false;
- notifyReleased();
- }
-
- /** Called when the {@link TaskView} has been released. */
- protected void notifyReleased() {
- if (mListener != null && mNotifiedForInitialized) {
- mListenerExecutor.execute(() -> {
- mListener.onReleased();
- });
- mNotifiedForInitialized = false;
- }
- }
-
- private void resetTaskInfo() {
- mTaskInfo = null;
- mTaskToken = null;
- mTaskLeash = null;
- }
-
- private void updateTaskVisibility() {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
- mSyncQueue.queue(wct);
- if (mListener == null) {
- return;
- }
- int taskId = mTaskInfo.taskId;
- mSyncQueue.runInSync((t) -> {
- mListenerExecutor.execute(() -> {
- mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
- });
- });
- }
-
- @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;
-
- if (mSurfaceCreated) {
- // Surface is ready, so just reparent the task to this surface control
- mTransaction.reparent(mTaskLeash, getSurfaceControl())
- .show(mTaskLeash)
- .apply();
- } else {
- // The surface has already been destroyed before the task has appeared,
- // so go ahead and hide the task entirely
- updateTaskVisibility();
- }
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
- onLocationChanged();
- if (taskInfo.taskDescription != null) {
- int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
- mSyncQueue.runInSync((t) -> {
- setResizeBackgroundColor(t, backgroundColor);
- });
- }
-
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- final ComponentName baseActivity = taskInfo.baseActivity;
- mListenerExecutor.execute(() -> {
- mListener.onTaskCreated(taskId, baseActivity);
- });
- }
- }
-
- @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) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onTaskRemovalStarted(taskId);
- });
- }
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
-
- // Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
- resetTaskInfo();
- }
-
- @Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
- }
- }
-
- @Override
- public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
- if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
- if (mListener != null) {
- final int taskId = taskInfo.taskId;
- mListenerExecutor.execute(() -> {
- mListener.onBackPressedOnTaskRoot(taskId);
- });
- }
- }
-
- @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 || mTaskLeash == null || mTaskInfo.taskId != taskId) {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- return mTaskLeash;
- }
-
- @Override
- public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
+ mTaskViewTaskController.release();
}
@Override
public String toString() {
- return "TaskView" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
+ return mTaskViewTaskController.toString();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
- mSurfaceCreated = true;
- mIsInitialized = true;
- notifyInitialized();
- mShellExecutor.execute(() -> {
- if (mTaskToken == null) {
- // 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)
- .apply();
- updateTaskVisibility();
- });
- }
-
- /** Called when the {@link TaskView} is initialized. */
- protected void notifyInitialized() {
- if (mListener != null && !mNotifiedForInitialized) {
- mNotifiedForInitialized = true;
- mListenerExecutor.execute(() -> {
- mListener.onInitialized();
- });
- }
+ mTaskViewTaskController.surfaceCreated(getSurfaceControl());
}
@Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if (mTaskToken == null) {
- return;
- }
- onLocationChanged();
+ public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
+ int width, int height) {
+ getBoundsOnScreen(mTmpRect);
+ mTaskViewTaskController.setWindowBounds(mTmpRect);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
- mSurfaceCreated = false;
- mShellExecutor.execute(() -> {
- if (mTaskToken == null) {
- // Nothing to update, task is not yet available
- return;
- }
-
- if (isUsingShellTransitions()) {
- mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
- return;
- }
-
- // Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
- updateTaskVisibility();
- });
+ mTaskViewTaskController.surfaceDestroyed();
}
@Override
@@ -514,89 +245,6 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
/** Returns the task info for the task in the TaskView. */
@Nullable
public 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
- updateWindowBounds(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 */);
- }
- });
- }
+ return mTaskViewTaskController.getTaskInfo();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
new file mode 100644
index 000000000000..3d0a8fd83819
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewBase.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.app.ActivityManager;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+/**
+ * A stub for SurfaceView used by {@link TaskViewTaskController}
+ */
+public interface TaskViewBase {
+ /**
+ * Returns the current bounds on screen for the task view.
+ * @return
+ */
+ // TODO(b/266242294): Remove getBoundsOnScreen() and instead send the bounds from the TaskView
+ // to TaskViewTaskController.
+ Rect getCurrentBoundsOnScreen();
+
+ /**
+ * This method should set the resize background color on the SurfaceView that is exposed to
+ * clients.
+ * See {@link android.view.SurfaceView#setResizeBackgroundColor(SurfaceControl.Transaction,
+ * int)}
+ */
+ void setResizeBgColor(SurfaceControl.Transaction transaction, int color);
+
+ /**
+ * Called when a task appears on the TaskView. See
+ * {@link TaskViewTaskController#onTaskAppeared(ActivityManager.RunningTaskInfo,
+ * SurfaceControl)} for details.
+ */
+ default void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ }
+
+ /**
+ * Called when a task is vanished from the TaskView. See
+ * {@link TaskViewTaskController#onTaskVanished(ActivityManager.RunningTaskInfo)} for details.
+ */
+ default void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ }
+
+ /**
+ * Called when the task in the TaskView is changed. See
+ * {@link TaskViewTaskController#onTaskInfoChanged(ActivityManager.RunningTaskInfo)} for details.
+ */
+ default void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ }
+}
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 42844b57b92a..735d9bce2059 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -57,7 +57,8 @@ public class TaskViewFactoryController {
/** Creates an {@link TaskView} */
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
- TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
+ TaskView taskView = new TaskView(context, new TaskViewTaskController(context,
+ mTaskOrganizer, mTaskViewTransitions, mSyncQueue));
executor.execute(() -> {
onCreate.accept(taskView);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
new file mode 100644
index 000000000000..080b171f4d40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
@@ -0,0 +1,516 @@
+/*
+ * 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;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.CloseGuard;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+/**
+ * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
+ * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
+ *
+ * The reverse communication is done via the {@link TaskViewBase} interface.
+ */
+public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
+
+ private final CloseGuard mGuard = new CloseGuard();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Executor mShellExecutor;
+ private final SyncTransactionQueue mSyncQueue;
+ private final TaskViewTransitions mTaskViewTransitions;
+ private TaskViewBase mTaskViewBase;
+ private final Context mContext;
+
+ protected ActivityManager.RunningTaskInfo mTaskInfo;
+ private WindowContainerToken mTaskToken;
+ private SurfaceControl mTaskLeash;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private boolean mSurfaceCreated;
+ private SurfaceControl mSurfaceControl;
+ private boolean mIsInitialized;
+ private boolean mNotifiedForInitialized;
+ private TaskView.Listener mListener;
+ private Executor mListenerExecutor;
+
+ public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
+ TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+ mContext = context;
+ mTaskOrganizer = organizer;
+ mShellExecutor = organizer.getExecutor();
+ mSyncQueue = syncQueue;
+ mTaskViewTransitions = taskViewTransitions;
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.addTaskView(this);
+ }
+ mGuard.open("release");
+ }
+
+ /**
+ * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
+ * task related changes and getting the current bounds.
+ */
+ public void setTaskViewBase(TaskViewBase taskViewBase) {
+ mTaskViewBase = taskViewBase;
+ }
+
+ /**
+ * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+ */
+ public boolean isInitialized() {
+ return mIsInitialized;
+ }
+
+ /** Until all users are converted, we may have mixed-use (eg. Car). */
+ private boolean isUsingShellTransitions() {
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
+ }
+
+ /**
+ * Only one listener may be set on the view, throws an exception otherwise.
+ */
+ void setListener(@NonNull Executor executor, TaskView.Listener listener) {
+ if (mListener != null) {
+ throw new IllegalStateException(
+ "Trying to set a listener when one has already been set");
+ }
+ mListener = listener;
+ mListenerExecutor = executor;
+ }
+
+ /**
+ * Launch an activity represented by {@link ShortcutInfo}.
+ * <p>The owner of this container must be allowed to access the shortcut information,
+ * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+ *
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+ @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, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Launch a new activity.
+ *
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ * @param launchBounds the bounds (window size and position) that the activity should be
+ * launched in, in pixels and in screen coordinates.
+ */
+ 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, options.getLaunchCookie());
+ });
+ return;
+ }
+ try {
+ pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
+ final Binder launchCookie = new Binder();
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
+ });
+ options.setLaunchBounds(launchBounds);
+ options.setLaunchCookie(launchCookie);
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ options.setRemoveWithTaskOrganizer(true);
+ }
+
+ /**
+ * Release this container if it is initialized.
+ */
+ public void release() {
+ performRelease();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ performRelease();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void performRelease() {
+ if (mTaskViewTransitions != null) {
+ mTaskViewTransitions.removeTaskView(this);
+ }
+ mShellExecutor.execute(() -> {
+ mTaskOrganizer.removeListener(this);
+ resetTaskInfo();
+ });
+ mGuard.close();
+ mIsInitialized = false;
+ notifyReleased();
+ }
+
+ /** Called when the {@link TaskViewTaskController} has been released. */
+ protected void notifyReleased() {
+ if (mListener != null && mNotifiedForInitialized) {
+ mListenerExecutor.execute(() -> {
+ mListener.onReleased();
+ });
+ mNotifiedForInitialized = false;
+ }
+ }
+
+ private void resetTaskInfo() {
+ mTaskInfo = null;
+ mTaskToken = null;
+ mTaskLeash = null;
+ }
+
+ private void updateTaskVisibility() {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ mSyncQueue.queue(wct);
+ if (mListener == null) {
+ return;
+ }
+ int taskId = mTaskInfo.taskId;
+ mSyncQueue.runInSync((t) -> {
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
+ });
+ });
+ }
+
+ @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;
+
+ if (mSurfaceCreated) {
+ // Surface is ready, so just reparent the task to this surface control
+ mTransaction.reparent(mTaskLeash, mSurfaceControl)
+ .show(mTaskLeash)
+ .apply();
+ } else {
+ // The surface has already been destroyed before the task has appeared,
+ // so go ahead and hide the task entirely
+ updateTaskVisibility();
+ }
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
+ mSyncQueue.runInSync((t) -> {
+ mTaskViewBase.onTaskAppeared(taskInfo, leash);
+ });
+
+ if (mListener != null) {
+ final int taskId = taskInfo.taskId;
+ final ComponentName baseActivity = taskInfo.baseActivity;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskCreated(taskId, baseActivity);
+ });
+ }
+ }
+
+ @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) {
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onTaskRemovalStarted(taskId);
+ });
+ }
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ resetTaskInfo();
+ mTaskViewBase.onTaskVanished(taskInfo);
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mTaskViewBase.onTaskInfoChanged(taskInfo);
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
+ if (mListener != null) {
+ final int taskId = taskInfo.taskId;
+ mListenerExecutor.execute(() -> {
+ mListener.onBackPressedOnTaskRoot(taskId);
+ });
+ }
+ }
+
+ @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 || mTaskLeash == null || mTaskInfo.taskId != taskId) {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ return mTaskLeash;
+ }
+
+ @Override
+ public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+
+ @Override
+ public String toString() {
+ return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
+ }
+
+ /**
+ * Should be called when the client surface is created.
+ *
+ * @param surfaceControl the {@link SurfaceControl} for the underlying surface.
+ */
+ public void surfaceCreated(SurfaceControl surfaceControl) {
+ mSurfaceCreated = true;
+ mIsInitialized = true;
+ mSurfaceControl = surfaceControl;
+ notifyInitialized();
+ mShellExecutor.execute(() -> {
+ if (mTaskToken == null) {
+ // 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, mSurfaceControl)
+ .show(mTaskLeash)
+ .apply();
+ updateTaskVisibility();
+ });
+ }
+
+ /**
+ * Sets the window bounds to {@code boundsOnScreen}.
+ * Call when view position or size has changed. Can also be called before the animation when
+ * the final bounds are known.
+ * Do not call during the animation.
+ *
+ * @param boundsOnScreen the on screen bounds of the surface view.
+ */
+ public void setWindowBounds(Rect boundsOnScreen) {
+ 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, boundsOnScreen);
+ mSyncQueue.queue(wct);
+ }
+
+ /** Should be called when the client surface is destroyed. */
+ public void surfaceDestroyed() {
+ mSurfaceCreated = false;
+ mSurfaceControl = null;
+ mShellExecutor.execute(() -> {
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+
+ if (isUsingShellTransitions()) {
+ mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+ return;
+ }
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ updateTaskVisibility();
+ });
+ }
+
+ /** Called when the {@link TaskViewTaskController} is initialized. */
+ protected void notifyInitialized() {
+ if (mListener != null && !mNotifiedForInitialized) {
+ mNotifiedForInitialized = true;
+ mListenerExecutor.execute(() -> {
+ mListener.onInitialized();
+ });
+ }
+ }
+
+ /** Returns the task info for the task in the TaskView. */
+ @Nullable
+ public 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);
+ });
+ }
+ mTaskViewBase.onTaskVanished(mTaskInfo);
+ 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, mSurfaceControl)
+ .show(mTaskLeash)
+ .apply();
+ // Also reparent on finishTransaction since the finishTransaction will reparent back
+ // to its "original" parent by default.
+ finishTransaction.reparent(mTaskLeash, mSurfaceControl)
+ .setPosition(mTaskLeash, 0, 0)
+ .apply();
+
+ wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
+ } 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();
+ mTaskViewBase.setResizeBgColor(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/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 07d501201105..306d6196c553 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -32,6 +32,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -41,7 +42,7 @@ import java.util.ArrayList;
public class TaskViewTransitions implements Transitions.TransitionHandler {
private static final String TAG = "TaskViewTransitions";
- private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
+ private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>();
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{ false };
@@ -54,14 +55,24 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
private static class PendingTransition {
final @WindowManager.TransitionType int mType;
final WindowContainerTransaction mWct;
- final @NonNull TaskView mTaskView;
+ final @NonNull TaskViewTaskController mTaskView;
IBinder mClaimed;
+ /**
+ * This is needed because arbitrary activity launches can still "intrude" into any
+ * transition since `startActivity` is a synchronous call. Once that is solved, we can
+ * remove this.
+ */
+ final IBinder mLaunchCookie;
+
PendingTransition(@WindowManager.TransitionType int type,
- @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
+ @Nullable WindowContainerTransaction wct,
+ @NonNull TaskViewTaskController taskView,
+ @Nullable IBinder launchCookie) {
mType = type;
mWct = wct;
mTaskView = taskView;
+ mLaunchCookie = launchCookie;
}
}
@@ -72,7 +83,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// TODO(210041388): register here once we have an explicit ordering mechanism.
}
- void addTaskView(TaskView tv) {
+ void addTaskView(TaskViewTaskController tv) {
synchronized (mRegistered) {
if (!mRegistered[0]) {
mRegistered[0] = true;
@@ -82,7 +93,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
mTaskViews.add(tv);
}
- void removeTaskView(TaskView tv) {
+ void removeTaskView(TaskViewTaskController tv) {
mTaskViews.remove(tv);
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
@@ -101,10 +112,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
* 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) {
+ private PendingTransition findPending(TaskViewTaskController 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) {
+ if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) {
return mPending.get(i);
}
if (latest) {
@@ -134,13 +146,13 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
if (triggerTask == null) {
return null;
}
- final TaskView taskView = findTaskView(triggerTask);
+ final TaskViewTaskController taskView = findTaskView(triggerTask);
if (taskView == null) return null;
// Opening types should all be initiated by shell
- if (!Transitions.isClosingType(request.getType())) return null;
+ if (!TransitionUtil.isClosingType(request.getType())) return null;
PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
if (pending == null) {
- pending = new PendingTransition(request.getType(), null, taskView);
+ pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
}
if (pending.mClaimed != null) {
throw new IllegalStateException("Task is closing in 2 collecting transitions?"
@@ -150,7 +162,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
return new WindowContainerTransaction();
}
- private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
+ private TaskViewTaskController 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)) {
@@ -160,12 +172,13 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
return null;
}
- void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
- mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
+ void startTaskView(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
+ mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie));
startNextTransition();
}
- void setTaskViewVisible(TaskView taskView, boolean visible) {
+ void setTaskViewVisible(TaskViewTaskController 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.
@@ -178,7 +191,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
pending = new PendingTransition(
- visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
+ visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */);
mPending.add(pending);
startNextTransition();
// visibility is reported in transition.
@@ -195,58 +208,92 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
@Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ if (!aborted) return;
+ final PendingTransition pending = findPending(transition);
+ if (pending == null) return;
+ mPending.remove(pending);
+ startNextTransition();
+ }
+
+ @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 (pending != null) {
+ mPending.remove(pending);
}
- if (tasks.isEmpty()) {
- Slog.e(TAG, "Got a TaskView transition with no task.");
+ if (mTaskViews.isEmpty()) {
+ if (pending != null) {
+ Slog.e(TAG, "Pending taskview transition but no task-views");
+ }
return false;
}
+ boolean stillNeedsMatchingLaunch = pending != null && pending.mLaunchCookie != null;
+ int changesHandled = 0;
WindowContainerTransaction wct = null;
- for (int i = 0; i < tasks.size(); ++i) {
- TransitionInfo.Change chg = tasks.get(i);
- if (Transitions.isClosingType(chg.getMode())) {
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change chg = info.getChanges().get(i);
+ if (chg.getTaskInfo() == null) continue;
+ if (TransitionUtil.isClosingType(chg.getMode())) {
final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
- TaskView tv = findTaskView(chg.getTaskInfo());
+ TaskViewTaskController tv = findTaskView(chg.getTaskInfo());
if (tv == null) {
- throw new IllegalStateException("TaskView transition is closing a "
- + "non-taskview task ");
+ if (pending != null) {
+ Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
+ + "shouldn't happen, so there may be a visual artifact: "
+ + chg.getTaskInfo().taskId);
+ }
+ continue;
}
if (isHide) {
tv.prepareHideAnimation(finishTransaction);
} else {
tv.prepareCloseAnimation();
}
- } else if (Transitions.isOpeningType(chg.getMode())) {
+ changesHandled++;
+ } else if (TransitionUtil.isOpeningType(chg.getMode())) {
final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
- if (wct == null) wct = new WindowContainerTransaction();
- TaskView tv = taskView;
- if (!taskIsNew) {
+ final TaskViewTaskController tv;
+ if (taskIsNew) {
+ if (pending == null
+ || !chg.getTaskInfo().containsLaunchCookie(pending.mLaunchCookie)) {
+ Slog.e(TAG, "Found a launching TaskView in the wrong transition. All "
+ + "TaskView launches should be initiated by shell and in their "
+ + "own transition: " + chg.getTaskInfo().taskId);
+ continue;
+ }
+ stillNeedsMatchingLaunch = false;
+ tv = pending.mTaskView;
+ } else {
tv = findTaskView(chg.getTaskInfo());
if (tv == null) {
- throw new IllegalStateException("TaskView transition is showing a "
- + "non-taskview task ");
+ if (pending != null) {
+ Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
+ + "shouldn't happen, so there may be a visual artifact: "
+ + chg.getTaskInfo().taskId);
+ }
+ continue;
}
}
+ if (wct == null) wct = new WindowContainerTransaction();
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());
+ changesHandled++;
}
}
+ if (stillNeedsMatchingLaunch) {
+ throw new IllegalStateException("Expected a TaskView launch in this transition but"
+ + " didn't get one.");
+ }
+ if (wct == null && pending == null && changesHandled != info.getChanges().size()) {
+ // Just some house-keeping, let another handler animate.
+ return false;
+ }
// No animation, just show it immediately.
startTransaction.apply();
finishTransaction.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 00b9fcede4ca..c767376d4f29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -22,7 +22,6 @@ import static android.graphics.Matrix.MTRANS_Y;
import android.annotation.CallSuper;
import android.graphics.Point;
import android.graphics.Rect;
-import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.view.animation.Transformation;
@@ -30,7 +29,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
@@ -71,7 +70,6 @@ class ActivityEmbeddingAnimationAdapter {
final float[] mVecs = new float[4];
@NonNull
final Rect mRect = new Rect();
- private boolean mIsFirstFrame = true;
private int mOverrideLayer = LAYER_NO_OVERRIDE;
ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
@@ -92,7 +90,7 @@ class ActivityEmbeddingAnimationAdapter {
mChange = change;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
- if (Transitions.isClosingType(change.getMode())) {
+ if (TransitionUtil.isClosingType(change.getMode())) {
// When it is closing, we want to show the content at the start position in case the
// window is resizing as well. For example, when the activities is changing from split
// to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
@@ -117,20 +115,21 @@ class ActivityEmbeddingAnimationAdapter {
mOverrideLayer = layer;
}
- /** Called on frame update. */
- final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
- if (mIsFirstFrame) {
- t.show(mLeash);
- if (mOverrideLayer != LAYER_NO_OVERRIDE) {
- t.setLayer(mLeash, mOverrideLayer);
- }
- mIsFirstFrame = false;
+ /** Called to prepare for the starting state. */
+ final void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction) {
+ startTransaction.show(mLeash);
+ if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+ startTransaction.setLayer(mLeash, mOverrideLayer);
}
+ mAnimation.getTransformationAt(0, mTransformation);
+ onAnimationUpdateInner(startTransaction);
+ }
+ /** Called on frame update. */
+ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
// Extract the transformation to the current time.
mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
mTransformation);
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
onAnimationUpdateInner(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 164d2f149931..1df6ecda78c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -32,6 +32,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
import android.util.Log;
+import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.window.TransitionInfo;
@@ -42,7 +43,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -130,11 +131,13 @@ class ActivityEmbeddingAnimationRunner {
animator.addUpdateListener((anim) -> {
// Update all adapters in the same transaction.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
}
t.apply();
});
+ prepareForFirstFrame(startTransaction, adapters);
}
animator.setDuration(duration);
animator.addListener(new Animator.AnimatorListener() {
@@ -181,7 +184,7 @@ class ActivityEmbeddingAnimationRunner {
if (isChangeTransition) {
return createChangeAnimationAdapters(info, startTransaction);
}
- if (Transitions.isClosingType(info.getType())) {
+ if (TransitionUtil.isClosingType(info.getType())) {
return createCloseAnimationAdapters(info);
}
return createOpenAnimationAdapters(info);
@@ -216,7 +219,7 @@ class ActivityEmbeddingAnimationRunner {
final Rect openingWholeScreenBounds = new Rect();
final Rect closingWholeScreenBounds = new Rect();
for (TransitionInfo.Change change : info.getChanges()) {
- if (Transitions.isOpeningType(change.getMode())) {
+ if (TransitionUtil.isOpeningType(change.getMode())) {
openingChanges.add(change);
openingWholeScreenBounds.union(change.getEndAbsBounds());
} else {
@@ -248,6 +251,15 @@ class ActivityEmbeddingAnimationRunner {
return adapters;
}
+ /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */
+ private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+ startTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ adapter.prepareForFirstFrame(startTransaction);
+ }
+ }
+
/** Adds edge extension to the surfaces that have such an animation property. */
private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -259,7 +271,7 @@ class ActivityEmbeddingAnimationRunner {
continue;
}
final TransitionInfo.Change change = adapter.mChange;
- if (Transitions.isOpeningType(adapter.mChange.getMode())) {
+ if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
// may not be visible or ready yet.
postStartTransactionCallbacks.add(
@@ -331,7 +343,7 @@ class ActivityEmbeddingAnimationRunner {
// When the parent window is also included in the transition as an opening window,
// we would like to animate the parent window instead.
final TransitionInfo.Change parentChange = info.getChange(parentToken);
- if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+ if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
// We won't create a separate animation for the parent, but to animate the
// parent for the child resizing.
handledChanges.add(parentChange);
@@ -392,7 +404,7 @@ class ActivityEmbeddingAnimationRunner {
// No-op if it will be covered by the changing parent window, or it is a changing
// window without bounds change.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
- } else if (Transitions.isClosingType(change.getMode())) {
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
shouldShouldBackgroundColor = false;
} else {
@@ -457,7 +469,7 @@ class ActivityEmbeddingAnimationRunner {
// When the parent window is also included in the transition as an opening window,
// we would like to animate the parent window instead.
final TransitionInfo.Change parentChange = info.getChange(parentToken);
- if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+ if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
changingChanges.add(parentChange);
}
}
@@ -479,8 +491,8 @@ class ActivityEmbeddingAnimationRunner {
// No-op if it will be covered by the changing parent window.
continue;
}
- hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
- hasClosingWindow |= Transitions.isClosingType(change.getMode());
+ hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
+ hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
}
return hasOpeningWindow && hasClosingWindow;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index d10a6744b5f1..cb8342a10a6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -36,7 +36,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
@@ -73,7 +73,7 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) {
// Noop but just keep the window showing/hiding.
- final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f;
+ final float alpha = TransitionUtil.isClosingType(change.getMode()) ? 0f : 1f;
return new AlphaAnimation(alpha, alpha);
}
@@ -198,7 +198,7 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = Transitions.isOpeningType(change.getMode());
+ final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
final Animation animation;
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
@@ -222,7 +222,7 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = Transitions.isOpeningType(change.getMode());
+ final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
final Animation animation;
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6ad168d..2ec9e8b12fc6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.animation;
+import android.graphics.Path;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@ public class Interpolators {
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
/**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
* The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
* is disappearing e.g. when moving off screen.
*/
@@ -81,4 +87,14 @@ public class Interpolators {
public static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 000000000000..36cf29a4c4f3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -0,0 +1,69 @@
+/*
+ * 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 android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+ private static final int BACKGROUND_LAYER = -1;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private SurfaceControl mBackgroundSurface;
+
+ public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface != null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+ Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+ final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+ .setName("back-animation-background")
+ .setCallsite("BackAnimationBackground")
+ .setColorLayer();
+
+ mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+ mBackgroundSurface = colorLayerBuilder.build();
+ transaction.setColor(mBackgroundSurface, colorComponents)
+ .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+ .show(mBackgroundSurface);
+ }
+
+ void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface == null) {
+ return;
+ }
+
+ if (mBackgroundSurface.isValid()) {
+ transaction.remove(mBackgroundSurface);
+ }
+ mBackgroundSurface = null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index aaeef196b618..349ff36ce8ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,9 +16,6 @@
package com.android.wm.shell.back;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -27,34 +24,33 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
-import android.app.WindowConfiguration;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
-import android.hardware.HardwareBuffer;
import android.hardware.input.InputManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Log;
-import android.view.IWindowFocusObserver;
+import android.util.SparseArray;
+import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.BackAnimationAdaptor;
+import android.window.BackAnimationAdapter;
import android.window.BackEvent;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
+import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
-import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
@@ -73,32 +69,28 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Controls the window animation run when a user initiates a back gesture.
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
- private static final String TAG = "BackAnimationController";
+ private static final String TAG = "ShellBackPreview";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
SETTING_VALUE_ON) == SETTING_VALUE_ON;
- /** Flag for U animation features */
+ /** Flag for U animation features */
public static boolean IS_U_ANIMATION_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
SETTING_VALUE_ON) == SETTING_VALUE_ON;
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
- // TODO (b/241808055) Find a appropriate time to remove during refactor
- private static final boolean USE_TRANSITION =
- SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0;
/**
- * Max duration to wait for a transition to finish before accepting another gesture start
- * request.
+ * Max duration to wait for an animation to finish before triggering the real back.
*/
- private static final long MAX_TRANSITION_DURATION = 2000;
+ private static final long MAX_ANIMATION_DURATION = 2000;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
- /** Tracks if an uninterruptible transition is in progress */
- private boolean mTransitionInProgress = false;
+ /** Tracks if an uninterruptible animation is in progress */
+ private boolean mPostCommitAnimationInProgress = false;
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
@@ -106,100 +98,61 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable
private BackNavigationInfo mBackNavigationInfo;
- private final SurfaceControl.Transaction mTransaction;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
- @Nullable
- private IOnBackInvokedCallback mBackToLauncherCallback;
- private float mTriggerThreshold;
- private final Runnable mResetTransitionRunnable = () -> {
- finishAnimation();
- mTransitionInProgress = false;
+ private final Runnable mAnimationTimeoutRunnable = () -> {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
+ MAX_ANIMATION_DURATION);
+ onBackAnimationFinished();
};
- private RemoteAnimationTarget mAnimationTarget;
- IBackAnimationRunner mIBackAnimationRunner;
- private IBackNaviAnimationController mBackAnimationController;
- private BackAnimationAdaptor mBackAnimationAdaptor;
+ private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
+ @VisibleForTesting
+ BackAnimationAdapter mBackAnimationAdapter;
private final TouchTracker mTouchTracker = new TouchTracker();
- private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
+
+ private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+ @Nullable
+ private IOnBackInvokedCallback mActiveCallback;
+
+ private CrossActivityAnimation mDefaultActivityAnimation;
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
@VisibleForTesting
- final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
- @Override
- public void focusGained(IBinder inputToken) { }
- @Override
- public void focusLost(IBinder inputToken) {
- mShellExecutor.execute(() -> {
- if (!mBackGestureStarted || mTransitionInProgress) {
- // If an uninterruptible transition is already in progress, we should ignore
- // this due to the transition may cause focus lost. (alpha = 0)
- return;
+ final RemoteCallback mNavigationObserver = new RemoteCallback(
+ new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mShellExecutor.execute(() -> {
+ if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+ // If an uninterruptible animation is already in progress, we should
+ // ignore this due to it may cause focus lost. (alpha = 0)
+ return;
+ }
+ ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
+ setTriggerBack(false);
+ onGestureFinished(false);
+ });
}
- setTriggerBack(false);
- onGestureFinished(false);
});
- }
- };
- /**
- * Cache the temporary callback and trigger result if gesture was finish before received
- * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
- */
- private class CachingBackDispatcher {
- private IOnBackInvokedCallback mOnBackCallback;
- private boolean mTriggerBack;
- // Whether we are waiting to receive onAnimationStart
- private boolean mWaitingAnimation;
-
- void startWaitingAnimation() {
- mWaitingAnimation = true;
- }
-
- boolean set(IOnBackInvokedCallback callback, boolean triggerBack) {
- if (mWaitingAnimation) {
- mOnBackCallback = callback;
- mTriggerBack = triggerBack;
- return true;
- }
- return false;
- }
-
- boolean consume() {
- boolean consumed = false;
- if (mWaitingAnimation && mOnBackCallback != null) {
- if (mTriggerBack) {
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(1);
- dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
- dispatchOnBackInvoked(mOnBackCallback);
- } else {
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(0);
- dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
- dispatchOnBackCancelled(mOnBackCallback);
- }
- startTransition();
- consumed = true;
- }
- mOnBackCallback = null;
- mWaitingAnimation = false;
- return consumed;
- }
- }
+ private final BackAnimationBackground mAnimationBackground;
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
- Context context) {
+ Context context,
+ @NonNull BackAnimationBackground backAnimationBackground) {
this(shellInit, shellController, shellExecutor, backgroundHandler,
- new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
- context, context.getContentResolver());
+ ActivityTaskManager.getService(), context, context.getContentResolver(),
+ backAnimationBackground);
}
@VisibleForTesting
@@ -208,17 +161,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
- @NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver) {
+ Context context, ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground) {
mShellController = shellController;
mShellExecutor = shellExecutor;
- mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
+ mAnimationBackground = backAnimationBackground;
}
@VisibleForTesting
@@ -228,8 +181,29 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+
+ initBackAnimationRunners();
+ }
+
+ private void initBackAnimationRunners() {
+ if (!IS_U_ANIMATION_ENABLED) {
+ return;
+ }
+
+ final CrossTaskBackAnimation crossTaskAnimation =
+ new CrossTaskBackAnimation(mContext, mAnimationBackground);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+ crossTaskAnimation.mBackAnimationRunner);
+ mDefaultActivityAnimation =
+ new CrossActivityAnimation(mContext, mAnimationBackground);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ mCustomizeActivityAnimation =
+ new CustomizeActivityAnimation(mContext, mAnimationBackground);
+ // TODO (236760237): register dialog close animation when it's completed.
}
private void setupAnimationDeveloperSettingsObserver(
@@ -254,8 +228,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
boolean isEnabled = settingValue == SETTING_VALUE_ON;
mEnableAnimations.set(isEnabled);
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
- isEnabled);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
public BackAnimation getBackAnimationImpl() {
@@ -306,21 +279,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
@Override
- public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
+ IRemoteAnimationRunner runner) {
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
- (controller) -> controller.setBackToLauncherCallback(callback));
+ (controller) -> controller.registerAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ new BackAnimationRunner(callback, runner)));
}
@Override
public void clearBackToLauncherCallback() {
executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
- (controller) -> controller.clearBackToLauncherCallback());
- }
-
- @Override
- public void onBackToLauncherAnimationFinished() {
- executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished",
- (controller) -> controller.onBackToLauncherAnimationFinished());
+ (controller) -> controller.unregisterAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME));
}
@Override
@@ -329,32 +300,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- @VisibleForTesting
- void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
- mBackToLauncherCallback = callback;
- if (USE_TRANSITION) {
- createAdaptor();
- }
- }
-
- private void clearBackToLauncherCallback() {
- mBackToLauncherCallback = null;
+ void registerAnimation(@BackNavigationInfo.BackTargetType int type,
+ @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
}
- @VisibleForTesting
- void onBackToLauncherAnimationFinished() {
- final boolean triggerBack = mTriggerBack;
- IOnBackInvokedCallback callback = mBackNavigationInfo != null
- ? mBackNavigationInfo.getOnBackInvokedCallback() : null;
- // Make sure the notification sequence should be controller > client.
- finishAnimation();
- if (callback != null) {
- if (triggerBack) {
- dispatchOnBackInvoked(callback);
- } else {
- dispatchOnBackCancelled(callback);
- }
- }
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
}
/**
@@ -363,7 +315,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
public void onMotionEvent(float touchX, float touchY, int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
@@ -380,7 +332,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
- onMove(touchX, touchY, swipeEdge);
+ onMove();
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
@@ -395,20 +347,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
- finishAnimation();
+ finishBackNavigation();
}
mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
mBackGestureStarted = true;
try {
- boolean requestAnimation = mEnableAnimations.get();
- mBackNavigationInfo = mActivityTaskManager.startBackNavigation(requestAnimation,
- mFocusObserver, mBackAnimationAdaptor);
+ mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
+ mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
- finishAnimation();
+ finishBackNavigation();
}
}
@@ -418,82 +369,28 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
Log.e(TAG, "Received BackNavigationInfo is null.");
return;
}
- int backType = backNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback = null;
- final boolean dispatchToLauncher = shouldDispatchToLauncher(backType);
- if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
- HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
- if (hardwareBuffer != null) {
- displayTargetScreenshot(hardwareBuffer,
- backNavigationInfo.getTaskWindowConfiguration());
- }
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- mTransaction.apply();
- } else if (dispatchToLauncher) {
- targetCallback = mBackToLauncherCallback;
- if (USE_TRANSITION) {
- mCachingBackDispatcher.startWaitingAnimation();
+ final int backType = backNavigationInfo.getType();
+ final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
+ if (shouldDispatchToAnimator) {
+ if (mAnimationDefinition.contains(backType)) {
+ mAnimationDefinition.get(backType).startGesture();
+ } else {
+ mActiveCallback = null;
}
- } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- }
- if (!USE_TRANSITION || !dispatchToLauncher) {
- dispatchOnBackStarted(
- targetCallback,
- mTouchTracker.createStartEvent(
- mBackNavigationInfo.getDepartingAnimationTarget()));
+ } else {
+ mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
}
}
- /**
- * 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. ");
+ private void onMove() {
+ if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
+ || mActiveCallback == null) {
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(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
- if (!mBackGestureStarted || mBackNavigationInfo == null) {
- return;
- }
final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
- if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
- dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
- } else if (mEnableAnimations.get()) {
- int backType = mBackNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback;
- if (shouldDispatchToLauncher(backType)) {
- targetCallback = mBackToLauncherCallback;
- } else {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- }
- dispatchOnBackProgressed(targetCallback, backEvent);
- }
+ dispatchOnBackProgressed(mActiveCallback, backEvent);
}
private void injectBackKey() {
@@ -509,68 +406,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
InputDevice.SOURCE_KEYBOARD);
ev.setDisplayId(mContext.getDisplay().getDisplayId());
- if (!InputManager.getInstance()
+ if (!mContext.getSystemService(InputManager.class)
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
Log.e(TAG, "Inject input event fail");
}
}
- private void onGestureFinished(boolean fromTouch) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted) {
- finishAnimation();
- return;
- }
-
- if (fromTouch) {
- // Let touch reset the flag otherwise it will start a new back navigation and refresh
- // the info when received a new move event.
- mBackGestureStarted = false;
- }
-
- if (mTransitionInProgress) {
- return;
- }
-
- if (mBackNavigationInfo == null) {
- // No focus window found or core are running recents animation, inject back key as
- // legacy behavior.
- if (mTriggerBack) {
- injectBackKey();
- }
- finishAnimation();
- return;
- }
-
- int backType = mBackNavigationInfo.getType();
- boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType);
- IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
- ? mBackToLauncherCallback
- : mBackNavigationInfo.getOnBackInvokedCallback();
- if (mCachingBackDispatcher.set(targetCallback, mTriggerBack)) {
- return;
- }
- if (shouldDispatchToLauncher) {
- startTransition();
- }
- if (mTriggerBack) {
- dispatchOnBackInvoked(targetCallback);
- } else {
- dispatchOnBackCancelled(targetCallback);
- }
- if (backType != BackNavigationInfo.TYPE_RETURN_TO_HOME || !shouldDispatchToLauncher) {
- // Launcher callback missing. Simply finish animation.
- finishAnimation();
- }
- }
-
- private boolean shouldDispatchToLauncher(int backType) {
- return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
- && mBackToLauncherCallback != null
- && mEnableAnimations.get()
+ private boolean shouldDispatchToAnimator() {
+ return mEnableAnimations.get()
&& mBackNavigationInfo != null
- && ((USE_TRANSITION && mBackNavigationInfo.isPrepareRemoteAnimation())
- || mBackNavigationInfo.getDepartingAnimationTarget() != null);
+ && mBackNavigationInfo.isPrepareRemoteAnimation();
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
@@ -579,7 +424,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackStarted(backEvent);
}
} catch (RemoteException e) {
@@ -603,7 +448,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackCancelled();
}
} catch (RemoteException e) {
@@ -617,7 +462,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackProgressed(backEvent);
}
} catch (RemoteException e) {
@@ -626,15 +471,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
- return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback)
- && mEnableAnimations.get();
+ // TODO(b/258698745): Only dispatch to animation callbacks.
+ return mEnableAnimations.get();
}
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
public void setTriggerBack(boolean triggerBack) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
mTriggerBack = triggerBack;
@@ -643,102 +488,221 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
mTouchTracker.setProgressThreshold(progressThreshold);
- mTriggerThreshold = triggerThreshold;
}
- private void finishAnimation() {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
- mTouchTracker.reset();
- BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
- boolean triggerBack = mTriggerBack;
- mBackNavigationInfo = null;
- mAnimationTarget = null;
- mTriggerBack = false;
- mShouldStartOnNextMoveEvent = false;
- if (backNavigationInfo == null) {
- return;
+ private void invokeOrCancelBack() {
+ // Make a synchronized call to core before dispatch back event to client side.
+ // If the close transition happens before the core receives onAnimationFinished, there will
+ // play a second close animation for that transition.
+ if (mBackAnimationFinishedCallback != null) {
+ try {
+ mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
+ }
+ mBackAnimationFinishedCallback = null;
}
- if (!USE_TRANSITION) {
- 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);
+ if (mBackNavigationInfo != null) {
+ final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(callback);
+ } else {
+ dispatchOnBackCancelled(callback);
}
- mTransaction.apply();
- }
- stopTransition();
- backNavigationInfo.onBackNavigationFinished(triggerBack);
- if (USE_TRANSITION) {
- final IBackNaviAnimationController controller = mBackAnimationController;
- if (controller != null) {
- try {
- controller.finish(triggerBack);
- } catch (RemoteException r) {
- // Oh no!
- }
+ }
+ finishBackNavigation();
+ }
+
+ /**
+ * Called when the gesture is released, then it could start the post commit animation.
+ */
+ private void onGestureFinished(boolean fromTouch) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted) {
+ finishBackNavigation();
+ return;
+ }
+
+ if (fromTouch) {
+ // Let touch reset the flag otherwise it will start a new back navigation and refresh
+ // the info when received a new move event.
+ mBackGestureStarted = false;
+ }
+
+ if (mPostCommitAnimationInProgress) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
+ return;
+ }
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
}
- mBackAnimationController = null;
+ finishBackNavigation();
+ return;
+ }
+
+ final int backType = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+ // Simply trigger and finish back navigation when no animator defined.
+ if (!shouldDispatchToAnimator() || runner == null) {
+ invokeOrCancelBack();
+ return;
}
+ if (runner.isWaitingAnimation()) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+ return;
+ } else if (runner.isAnimationCancelled()) {
+ invokeOrCancelBack();
+ return;
+ }
+ startPostCommitAnimation();
}
- private void startTransition() {
- if (mTransitionInProgress) {
+ /**
+ * Start the phase 2 animation when gesture is released.
+ * Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
+ */
+ private void startPostCommitAnimation() {
+ if (mPostCommitAnimationInProgress) {
return;
}
- mTransitionInProgress = true;
- mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
+ mPostCommitAnimationInProgress = true;
+ mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
+
+ // The next callback should be {@link #onBackAnimationFinished}.
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(mActiveCallback);
+ } else {
+ dispatchOnBackCancelled(mActiveCallback);
+ }
}
- private void stopTransition() {
- if (!mTransitionInProgress) {
+ /**
+ * Called when the post commit animation is completed or timeout.
+ * This will trigger the real {@link IOnBackInvokedCallback} behavior.
+ */
+ @VisibleForTesting
+ void onBackAnimationFinished() {
+ if (!mPostCommitAnimationInProgress) {
return;
}
- mShellExecutor.removeCallbacks(mResetTransitionRunnable);
- mTransitionInProgress = false;
+ // Stop timeout runner.
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+ mPostCommitAnimationInProgress = false;
+
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+ // Trigger the real back.
+ invokeOrCancelBack();
}
- private void createAdaptor() {
- mIBackAnimationRunner = new IBackAnimationRunner.Stub() {
- @Override
- public void onAnimationCancelled() {
- // no op for now
+ /**
+ * This should be called after the whole back navigation is completed.
+ */
+ @VisibleForTesting
+ void finishBackNavigation() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
+ mShouldStartOnNextMoveEvent = false;
+ mTouchTracker.reset();
+ mActiveCallback = null;
+ // reset to default
+ if (mDefaultActivityAnimation != null
+ && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mDefaultActivityAnimation.mBackAnimationRunner);
+ }
+ if (mBackNavigationInfo != null) {
+ mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+ mBackNavigationInfo = null;
+ }
+ mTriggerBack = false;
+ }
+
+ private BackAnimationRunner getAnimationRunnerAndInit() {
+ int type = mBackNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ final BackNavigationInfo.CustomAnimationInfo animationInfo =
+ mBackNavigationInfo.getCustomAnimationInfo();
+ if (animationInfo != null && mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.mBackAnimationRunner);
}
- @Override // Binder interface
- public void onAnimationStart(IBackNaviAnimationController controller, int type,
- RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps) {
+ }
+ return mAnimationDefinition.get(type);
+ }
+
+ private void createAdapter() {
+ IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(() -> {
- mBackAnimationController = controller;
- for (int i = 0; i < apps.length; i++) {
- final RemoteAnimationTarget target = apps[i];
- if (MODE_CLOSING == target.mode) {
- mAnimationTarget = target;
- } else if (MODE_OPENING == target.mode) {
- // TODO Home activity should handle the visibility for itself
- // once it finish relayout for orientation change
- SurfaceControl.Transaction tx =
- new SurfaceControl.Transaction();
- tx.setAlpha(target.leash, 1);
- tx.apply();
+ if (mBackNavigationInfo == null) {
+ Log.e(TAG, "Lack of navigation info to start animation.");
+ return;
+ }
+ final int type = mBackNavigationInfo.getType();
+ final BackAnimationRunner runner = getAnimationRunnerAndInit();
+ if (runner == null) {
+ Log.e(TAG, "Animation didn't be defined for type "
+ + BackNavigationInfo.typeToString(type));
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished(false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call IBackNaviAnimationController", e);
+ }
}
+ return;
+ }
+ mActiveCallback = runner.getCallback();
+ mBackAnimationFinishedCallback = finishedCallback;
+
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
+ runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
+ BackAnimationController.this::onBackAnimationFinished));
+
+ if (apps.length >= 1) {
+ dispatchOnBackStarted(
+ mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+ }
+
+ // Dispatch the first progress after animation start for smoothing the initial
+ // animation, instead of waiting for next onMove.
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation start, we have to
+ // trigger the uninterruptible transition to finish the back animation.
+ startPostCommitAnimation();
+ }
+ });
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ mShellExecutor.execute(() -> {
+ final BackAnimationRunner runner = mAnimationDefinition.get(
+ mBackNavigationInfo.getType());
+ if (runner == null) {
+ return;
}
- dispatchOnBackStarted(mBackToLauncherCallback,
- mTouchTracker.createStartEvent(mAnimationTarget));
- final BackMotionEvent backInit = mTouchTracker.createProgressEvent();
- if (!mCachingBackDispatcher.consume()) {
- dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+ runner.cancelAnimation();
+ if (!mBackGestureStarted) {
+ invokeOrCancelBack();
}
});
}
};
- mBackAnimationAdaptor = new BackAnimationAdaptor(mIBackAnimationRunner,
- BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
new file mode 100644
index 000000000000..913239f74bf2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.view.WindowManager.TRANSIT_OLD_UNSET;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.window.IBackAnimationRunner;
+import android.window.IOnBackInvokedCallback;
+
+/**
+ * Used to register the animation callback and runner, it will trigger result if gesture was finish
+ * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
+ * trigger the real back behavior.
+ */
+class BackAnimationRunner {
+ private static final String TAG = "ShellBackPreview";
+
+ private final IOnBackInvokedCallback mCallback;
+ private final IRemoteAnimationRunner mRunner;
+
+ // Whether we are waiting to receive onAnimationStart
+ private boolean mWaitingAnimation;
+
+ /** True when the back animation is cancelled */
+ private boolean mAnimationCancelled;
+
+ BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner) {
+ mCallback = callback;
+ mRunner = runner;
+ }
+
+ /** Returns the registered animation runner */
+ IRemoteAnimationRunner getRunner() {
+ return mRunner;
+ }
+
+ /** Returns the registered animation callback */
+ IOnBackInvokedCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Called from {@link IBackAnimationRunner}, it will deliver these
+ * {@link RemoteAnimationTarget}s to the corresponding runner.
+ */
+ void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
+ final IRemoteAnimationFinishedCallback callback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() {
+ finishedCallback.run();
+ }
+ };
+ mWaitingAnimation = false;
+ try {
+ getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
+ nonApps, callback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onAnimationStart", e);
+ }
+ }
+
+ void startGesture() {
+ mWaitingAnimation = true;
+ mAnimationCancelled = false;
+ }
+
+ boolean isWaitingAnimation() {
+ return mWaitingAnimation;
+ }
+
+ void cancelAnimation() {
+ mWaitingAnimation = false;
+ mAnimationCancelled = true;
+ }
+
+ boolean isAnimationCancelled() {
+ return mAnimationCancelled;
+ }
+
+ void resetWaitingAnimation() {
+ mWaitingAnimation = false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 000000000000..da113cb579d6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,410 @@
+/*
+ * 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 android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.util.TypedValue;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+ /**
+ * Minimum scale of the entering/closing window.
+ */
+ private static final float MIN_WINDOW_SCALE = 0.9f;
+
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 350;
+ private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final FloatProperty<CrossActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setEnteringProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getEnteringProgress();
+ }
+ };
+ private static final FloatProperty<CrossActivityAnimation> LEAVE_PROGRESS_PROP =
+ new FloatProperty<>("leave-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setLeavingProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getLeavingProgress();
+ }
+ };
+ private static final float MIN_WINDOW_ALPHA = 0.01f;
+ private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final int SCALE_FACTOR = 100;
+ // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
+ private static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+ private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringRect = new RectF();
+ private final SpringAnimation mEnteringProgressSpring;
+ private final SpringAnimation mLeavingProgressSpring;
+ // Max window x-shift in pixels.
+ private final float mWindowXShift;
+
+ private float mEnteringProgress = 0f;
+ private float mLeavingProgress = 0f;
+
+ private final PointF mInitialTouchPos = new PointF();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+
+ private final BackAnimationBackground mBackground;
+
+ CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
+ mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mEnteringProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP);
+ mLeavingProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP,
+ context.getResources().getDisplayMetrics());
+ }
+
+ /**
+ * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two.
+ * From https://en.wikipedia.org/wiki/Smoothstep
+ */
+ private static float smoothstep(float edge0, float edge1, float x) {
+ if (x < edge0) return 0;
+ if (x >= edge1) return 1;
+
+ x = (x - edge0) / (edge1 - edge0);
+ return x * x * (3 - 2 * x);
+ }
+
+ /**
+ * Linearly map x from range (a1, a2) to range (b1, b2).
+ */
+ private static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+ }
+
+ /**
+ * Linearly map a normalized value from (0, 1) to (min, max).
+ */
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ mTransaction.setAnimationTransaction();
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background with task background color.
+ mBackground.ensureBackground(
+ mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ }
+
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setAlpha(leash, targetAlpha)
+ .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, mCornerRadius);
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+
+ mTransaction.apply();
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mInitialTouchPos.set(0, 0);
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ mEnteringProgressSpring.animateToFinalPosition(0);
+ mEnteringProgressSpring.skipToEnd();
+ mLeavingProgressSpring.animateToFinalPosition(0);
+ mLeavingProgressSpring.skipToEnd();
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mBackInProgress = true;
+ }
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+ float progress = backEvent.getProgress();
+ float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
+ ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+ mLeavingProgressSpring.animateToFinalPosition(springProgress);
+ mEnteringProgressSpring.animateToFinalPosition(springProgress);
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+ // End the fade animations
+ mLeavingProgressSpring.cancel();
+ mEnteringProgressSpring.cancel();
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringRect.round(mEnteringStartRect);
+ mTransaction.hide(mClosingTarget.leash);
+
+ ValueAnimator valueAnimator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+ float alpha = mapRange(progress, mEnteringProgress, 1.0f);
+
+ mEnteringRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+ }
+
+ private float getEnteringProgress() {
+ return mEnteringProgress * SCALE_FACTOR;
+ }
+
+ private void setEnteringProgress(float value) {
+ mEnteringProgress = value / SCALE_FACTOR;
+ if (mEnteringTarget != null && mEnteringTarget.leash != null) {
+ transformWithProgress(
+ mEnteringProgress,
+ Math.max(
+ smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+ MIN_WINDOW_ALPHA), /* alpha */
+ mEnteringTarget.leash,
+ mEnteringRect,
+ -mWindowXShift,
+ 0
+ );
+ }
+ }
+
+ private float getLeavingProgress() {
+ return mLeavingProgress * SCALE_FACTOR;
+ }
+
+ private void setLeavingProgress(float value) {
+ mLeavingProgress = value / SCALE_FACTOR;
+ if (mClosingTarget != null && mClosingTarget.leash != null) {
+ transformWithProgress(
+ mLeavingProgress,
+ Math.max(
+ 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA),
+ mClosingTarget.leash,
+ mClosingRect,
+ 0,
+ mWindowXShift
+ );
+ }
+ }
+
+ private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
+ RectF targetRect, float deltaXMin, float deltaXMax) {
+ final float touchY = mTouchPos.y;
+
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress);
+ final float closingScale = MIN_WINDOW_SCALE
+ + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE);
+ final float closingWidth = closingScale * width;
+ final float closingHeight = (float) height / width * closingWidth;
+
+ // Move the window along the X axis.
+ float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+ closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
+
+ // Move the window along the Y axis.
+ final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ final float closingTop = (height - closingHeight) * 0.5f;
+ targetRect.set(
+ closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+
+ applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA));
+ mTransaction.apply();
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackMotionEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 000000000000..99a434aff799
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,361 @@
+/*
+ * 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 android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+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.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+ private static final int BACKGROUNDCOLOR = 0x43433A;
+
+ /**
+ * Minimum scale of the entering window.
+ */
+ private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+ /**
+ * Minimum scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+ /**
+ * Minimum color scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+ /**
+ * The margin between the entering window and the closing window
+ */
+ private static final int WINDOW_MARGIN = 35;
+
+ /** Max window translation in the Y axis. */
+ private static final int WINDOW_MAX_DELTA_Y = 160;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingCurrentRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringCurrentRect = new RectF();
+
+ private final PointF mInitialTouchPos = new PointF();
+ private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final float[] mTmpTranslate = {0, 0, 0};
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private boolean mIsRightEdge;
+ private float mProgress = 0;
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+ private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+
+ private final BackAnimationBackground mBackground;
+
+ CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
+ }
+
+ private float getInterpolatedProgress(float backProgress) {
+ return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background.
+ mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
+ }
+
+ private void updateGestureBackProgress(float progress, BackEvent event) {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ return;
+ }
+
+ float touchX = event.getTouchX();
+ float touchY = event.getTouchY();
+ float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+ // The 'follow width' is the width of the window if it completely matches
+ // the gesture displacement.
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ // The 'progress width' is the width of the window if it strictly linearly interpolates
+ // to minimum scale base on progress.
+ float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+ float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+ float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+ // The final width is derived from interpolating between the follow with and progress width
+ // using gesture progress.
+ float enteringWidth = enteringScale * width;
+ float closingWidth = closingScale * width;
+ float enteringHeight = (float) height / width * enteringWidth;
+ float closingHeight = (float) height / width * closingWidth;
+
+ float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+ // Move the window along the Y axis.
+ float closingTop = (height - closingHeight) * 0.5f + deltaY;
+ float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float right = width - (progress * WINDOW_MARGIN);
+ float left = right - closingWidth;
+
+ mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+ mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+ left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+ applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+ applyColorTransform(mClosingTarget.leash, closingColorScale);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ mTransaction.apply();
+ }
+
+ private void updatePostCommitClosingAnimation(float progress) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ float alpha = mapRange(progress, 1, 0);
+ mTransaction.setAlpha(mClosingTarget.leash, alpha);
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+ mEnteringCurrentRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ }
+
+ /** Transform the target window to match the target rect. */
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+ if (leash == null) {
+ return;
+ }
+
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, cornerRadius);
+ }
+
+ private void applyColorTransform(SurfaceControl leash, float colorScale) {
+ if (leash == null) {
+ return;
+ }
+ computeScaleTransformMatrix(colorScale, mTmpFloat9);
+ mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+ }
+
+ static void computeScaleTransformMatrix(float scale, float[] matrix) {
+ matrix[0] = scale;
+ matrix[1] = 0;
+ matrix[2] = 0;
+ matrix[3] = 0;
+ matrix[4] = scale;
+ matrix[5] = 0;
+ matrix[6] = 0;
+ matrix[7] = 0;
+ matrix[8] = scale;
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+
+ mTransaction.apply();
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mClosingCurrentRect.setEmpty();
+ mInitialTouchPos.set(0, 0);
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+ mBackInProgress = true;
+ }
+ mProgress = backEvent.getProgress();
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringCurrentRect.round(mEnteringStartRect);
+
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+ valueAnimator.setInterpolator(mInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ updatePostCommitClosingAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackMotionEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossTaskBackAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.onBackCancelled(CrossTaskBackAnimation.this::finishAnimation);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ };
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
new file mode 100644
index 000000000000..ae33b9445acd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.view.Choreographer;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackNavigationInfo;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Class that handle customized close activity transition animation.
+ */
+@ShellMainThread
+class CustomizeActivityAnimation {
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+ private final float mCornerRadius;
+ private final SurfaceControl.Transaction mTransaction;
+ private final BackAnimationBackground mBackground;
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 250;
+
+ private static final int SCALE_FACTOR = 1000;
+ private final SpringAnimation mProgressSpring;
+ private float mLatestProgress = 0.0f;
+
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+ final CustomAnimationLoader mCustomAnimationLoader;
+ private Animation mEnterAnimation;
+ private Animation mCloseAnimation;
+ final Transformation mTransformation = new Transformation();
+
+ private final Choreographer mChoreographer;
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ this(context, background, new SurfaceControl.Transaction(), null);
+ }
+
+ CustomizeActivityAnimation(Context context, BackAnimationBackground background,
+ SurfaceControl.Transaction transaction, Choreographer choreographer) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackground = background;
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mCustomAnimationLoader = new CustomAnimationLoader(context);
+
+ mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
+ mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+ }
+
+ private float getLatestProgress() {
+ return mLatestProgress * SCALE_FACTOR;
+ }
+ private void setLatestProgress(float value) {
+ mLatestProgress = value / SCALE_FACTOR;
+ applyTransformTransaction(mLatestProgress);
+ }
+
+ private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter") {
+ @Override
+ public void setValue(CustomizeActivityAnimation anim, float value) {
+ anim.setLatestProgress(value);
+ }
+
+ @Override
+ public Float get(CustomizeActivityAnimation object) {
+ return object.getLatestProgress();
+ }
+ };
+
+ // The target will lose focus when alpha == 0, so keep a minimum value for it.
+ private static float keepMinimumAlpha(float transAlpha) {
+ return Math.max(transAlpha, 0.005f);
+ }
+
+ private static void initializeAnimation(Animation animation, Rect bounds) {
+ final int width = bounds.width();
+ final int height = bounds.height();
+ animation.initialize(width, height, width, height);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
+ initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
+
+ // Draw background with task background color.
+ if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
+ mBackground.ensureBackground(
+ mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ }
+ }
+
+ private void applyTransformTransaction(float progress) {
+ if (mClosingTarget == null || mEnteringTarget == null) {
+ return;
+ }
+ applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
+ applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ }
+
+ private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
+ mTransformation.clear();
+ animation.getTransformationAt(progress, mTransformation);
+ mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
+ mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
+ mTransaction.setCornerRadius(leash, mCornerRadius);
+ }
+
+ void finishAnimation() {
+ if (mCloseAnimation != null) {
+ mCloseAnimation.reset();
+ mCloseAnimation = null;
+ }
+ if (mEnterAnimation != null) {
+ mEnterAnimation.reset();
+ mEnterAnimation = null;
+ }
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+ mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+ mTransaction.apply();
+ mTransformation.clear();
+ mLatestProgress = 0;
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ mProgressSpring.animateToFinalPosition(0);
+ mProgressSpring.skipToEnd();
+ }
+
+ void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ return;
+ }
+
+ final float progress = backEvent.getProgress();
+
+ float springProgress = (progress > 0.1f
+ ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
+ : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+
+ mProgressSpring.animateToFinalPosition(springProgress);
+ }
+
+ static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+ }
+
+ void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null
+ || mCloseAnimation == null || mEnterAnimation == null) {
+ finishAnimation();
+ return;
+ }
+ mProgressSpring.cancel();
+
+ // Enter phase 2 of the animation
+ final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
+ .setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(mDecelerateInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = (float) animation.getAnimatedValue();
+ applyTransformTransaction(progress);
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ /**
+ * Load customize animation before animation start.
+ */
+ boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ mCloseAnimation = mCustomAnimationLoader.load(
+ animationInfo, false /* enterAnimation */);
+ if (mCloseAnimation != null) {
+ mEnterAnimation = mCustomAnimationLoader.load(
+ animationInfo, true /* enterAnimation */);
+ return true;
+ }
+ return false;
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackMotionEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CustomizeActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+ if (mCloseAnimation == null || mEnterAnimation == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "No animation loaded, should choose cross-activity animation?");
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+
+ /**
+ * Helper class to load custom animation.
+ */
+ static class CustomAnimationLoader {
+ private final TransitionAnimation mTransitionAnimation;
+
+ CustomAnimationLoader(Context context) {
+ mTransitionAnimation = new TransitionAnimation(
+ context, false /* debug */, "CustomizeBackAnimation");
+ }
+
+ Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
+ boolean enterAnimation) {
+ final String packageName = animationInfo.getPackageName();
+ if (packageName.isEmpty()) {
+ return null;
+ }
+ final int windowAnimations = animationInfo.getWindowAnimations();
+ if (windowAnimations == 0) {
+ return null;
+ }
+ final int attrs = enterAnimation
+ ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+ Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
+ attrs, false /* translucent */);
+ // Only allow to load default animation for opening target.
+ if (a == null && enterAnimation) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
+ }
+ if (a != null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
+ } else {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
+ }
+ return a;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
index 6311f879fd45..2b2a0e397792 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl
@@ -17,29 +17,21 @@
package com.android.wm.shell.back;
import android.window.IOnBackInvokedCallback;
+import android.view.IRemoteAnimationRunner;
/**
* Interface for Launcher process to register back invocation callbacks.
*/
interface IBackAnimation {
-
/**
- * Sets a {@link IOnBackInvokedCallback} to be invoked when
+ * Sets a {@link IOnBackInvokedCallback} and a {@link IRemoteAnimationRunner} to be invoked when
* back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}.
*/
- void setBackToLauncherCallback(in IOnBackInvokedCallback callback);
+ void setBackToLauncherCallback(in IOnBackInvokedCallback callback,
+ in IRemoteAnimationRunner runner);
/**
* 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/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
new file mode 100644
index 000000000000..837d5ff3b073
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+ "presubmit": [
+ {
+ "name": "WMShellUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "com.android.wm.shell.back"
+ }
+ ]
+ },
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackGestureInvokedTest"
+ },
+ {
+ "include-filter": "android.server.wm.BackNavigationTests"
+ },
+ {
+ "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 541c0f04b9b9..48fe65d3ce59 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
@@ -69,10 +69,14 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.view.IWindowManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotSync;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -177,6 +181,7 @@ public class BubbleController implements ConfigurationChangeListener {
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
+ private final IWindowManager mWmService;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -267,7 +272,8 @@ public class BubbleController implements ConfigurationChangeListener {
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ IWindowManager wmService) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -299,6 +305,7 @@ public class BubbleController implements ConfigurationChangeListener {
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
+ mWmService = wmService;
shellInit.addInitCallback(this::onInit, this);
}
@@ -709,10 +716,18 @@ public class BubbleController implements ConfigurationChangeListener {
return;
}
+ mAddedToWindowManager = false;
+ // Put on background for this binder call, was causing jank
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ // Not sure if this happens in production, but was happening in tests
+ // (b/253647225)
+ e.printStackTrace();
+ }
+ });
try {
- mAddedToWindowManager = false;
- // Put on background for this binder call, was causing jank
- mBackgroundExecutor.execute(() -> mContext.unregisterReceiver(mBroadcastReceiver));
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mBubbleData.getOverflow().cleanUpExpandedState();
@@ -730,7 +745,7 @@ public class BubbleController implements ConfigurationChangeListener {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1013,19 +1028,21 @@ public class BubbleController implements ConfigurationChangeListener {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
*/
- public void showOrHideAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) {
Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
return;
}
- PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+ PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
@@ -1046,13 +1063,33 @@ public class BubbleController implements ConfigurationChangeListener {
}
} else {
// App bubble does not exist, lets add and expand it
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ Bubble b = new Bubble(intent, user, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
}
/**
+ * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
+ * can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
+ *
+ * TODO(b/267324693): Implement the exclude layer functionality in screenshot.
+ */
+ public void getScreenshotExcludingBubble(int displayId,
+ Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener) {
+ try {
+ mWmService.captureDisplay(displayId, null, screenCaptureListener.first);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to capture screenshot");
+ }
+ }
+
+ /** Sets the app bubble's taskId which is cached for SysUI. */
+ public void setAppBubbleTaskId(int taskId) {
+ mImpl.mCachedState.setAppBubbleTaskId(taskId);
+ }
+
+ /**
* Fills the overflow bubbles by loading them from disk.
*/
void loadOverflowBubblesFromDisk() {
@@ -1692,6 +1729,7 @@ public class BubbleController implements ConfigurationChangeListener {
private HashSet<String> mSuppressedBubbleKeys = new HashSet<>();
private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
+ private int mAppBubbleTaskId = INVALID_TASK_ID;
private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();
@@ -1723,12 +1761,22 @@ public class BubbleController implements ConfigurationChangeListener {
mSuppressedBubbleKeys.clear();
mShortcutIdToBubble.clear();
+ mAppBubbleTaskId = INVALID_TASK_ID;
for (Bubble b : mTmpBubbles) {
mShortcutIdToBubble.put(b.getShortcutId(), b);
updateBubbleSuppressedState(b);
+
+ if (KEY_APP_BUBBLE.equals(b.getKey())) {
+ mAppBubbleTaskId = b.getTaskId();
+ }
}
}
+ /** Sets the app bubble's taskId which is cached for SysUI. */
+ synchronized void setAppBubbleTaskId(int taskId) {
+ mAppBubbleTaskId = taskId;
+ }
+
/**
* Updates a specific bubble suppressed state. This is used mainly because notification
* suppression changes don't go through the same BubbleData update mechanism.
@@ -1778,6 +1826,8 @@ public class BubbleController implements ConfigurationChangeListener {
for (String key : mSuppressedGroupToNotifKeys.keySet()) {
pw.println(" suppressing: " + key);
}
+
+ pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId);
}
}
@@ -1821,10 +1871,27 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void showOrHideAppBubble(Intent intent) {
- mMainExecutor.execute(() -> {
- BubbleController.this.showOrHideAppBubble(intent);
- });
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.showOrHideAppBubble(intent, user));
+ }
+
+ @Override
+ public boolean isAppBubbleTaskId(int taskId) {
+ return mCachedState.mAppBubbleTaskId == taskId;
+ }
+
+ @Override
+ @Nullable
+ public ScreenshotSync getScreenshotExcludingBubble(int displayId) {
+ Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener =
+ ScreenCapture.createSyncCaptureListener();
+
+ mMainExecutor.execute(
+ () -> BubbleController.this.getScreenshotExcludingBubble(displayId,
+ screenCaptureListener));
+
+ return screenCaptureListener.second;
}
@Override
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 5ea370b65407..9ccd6ebc51e2 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -53,13 +54,13 @@ import android.util.IntProperty;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.window.ScreenCapture;
import androidx.annotation.Nullable;
@@ -67,6 +68,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTaskController;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
@@ -140,6 +142,7 @@ public class BubbleExpandedView extends LinearLayout {
private AlphaOptimizedButton mManageButton;
private TaskView mTaskView;
+ private TaskViewTaskController mTaskViewTaskController;
private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -224,6 +227,9 @@ public class BubbleExpandedView extends LinearLayout {
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
@@ -231,11 +237,19 @@ public class BubbleExpandedView extends LinearLayout {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
if (mBubble.isAppBubble()) {
- PendingIntent pi = PendingIntent.getActivity(mContext, 0,
- mBubble.getAppBubbleIntent(),
- PendingIntent.FLAG_MUTABLE,
- null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ PendingIntent pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ mBubble.getAppBubbleIntent()
+ .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+ PendingIntent.FLAG_IMMUTABLE,
+ /* options= */ null);
+ mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+ launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
@@ -273,6 +287,11 @@ public class BubbleExpandedView extends LinearLayout {
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
+ if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) {
+ // Let the controller know sooner what the taskId is.
+ mController.setAppBubbleTaskId(mTaskId);
+ }
+
// With the task org, the taskAppeared callback will only happen once the task has
// already drawn
setContentVisibility(true);
@@ -406,8 +425,10 @@ public class BubbleExpandedView extends LinearLayout {
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
+ mTaskViewTaskController = new TaskViewTaskController(mContext,
+ mController.getTaskOrganizer(),
mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
mExpandedViewContainer.addView(mTaskView);
bringChildToFront(mTaskView);
@@ -516,7 +537,7 @@ public class BubbleExpandedView extends LinearLayout {
/** Return a GraphicBuffer with the contents of the task view surface. */
@Nullable
- SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
+ ScreenCapture.ScreenshotHardwareBuffer snapshotActivitySurface() {
if (mIsOverflow) {
// For now, just snapshot the view and return it as a hw buffer so that the animation
// code for both the tasks and overflow can be the same
@@ -525,7 +546,7 @@ public class BubbleExpandedView extends LinearLayout {
p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight()));
p.endRecording();
Bitmap snapshot = Bitmap.createBitmap(p);
- return new SurfaceControl.ScreenshotHardwareBuffer(
+ return new ScreenCapture.ScreenshotHardwareBuffer(
snapshot.getHardwareBuffer(),
snapshot.getColorSpace(),
false /* containsSecureLayers */,
@@ -534,7 +555,7 @@ public class BubbleExpandedView extends LinearLayout {
if (mTaskView == null || mTaskView.getSurfaceControl() == null) {
return null;
}
- return SurfaceControl.captureLayers(
+ return ScreenCapture.captureLayers(
mTaskView.getSurfaceControl(),
new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()),
1 /* scale */);
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 5ecbd6b596b6..deb4fd5f19bb 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
@@ -51,7 +51,6 @@ import android.util.Log;
import android.view.Choreographer;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
@@ -64,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
+import android.window.ScreenCapture;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -238,7 +238,7 @@ public class BubbleStackView extends FrameLayout
* Buffer containing a screenshot of the animating-out bubble. This is drawn into the
* SurfaceView during animations.
*/
- private SurfaceControl.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer;
+ private ScreenCapture.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer;
private BubbleFlyoutView mFlyout;
/** Runnable that fades out the flyout and then sets it to GONE. */
@@ -844,6 +844,8 @@ public class BubbleStackView extends FrameLayout
private DismissView mDismissView;
private ViewGroup mManageMenu;
+ private TextView mManageDontBubbleText;
+ private ViewGroup mManageSettingsView;
private ImageView mManageSettingsIcon;
private TextView mManageSettingsText;
private boolean mShowingManage = false;
@@ -918,7 +920,6 @@ public class BubbleStackView extends FrameLayout
addView(mAnimatingOutSurfaceContainer);
mAnimatingOutSurfaceView = new SurfaceView(getContext());
- mAnimatingOutSurfaceView.setUseAlpha();
mAnimatingOutSurfaceView.setZOrderOnTop(true);
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources());
@@ -1218,7 +1219,11 @@ public class BubbleStackView extends FrameLayout
mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
});
- mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+ mManageDontBubbleText = mManageMenu
+ .findViewById(R.id.bubble_manage_menu_dont_bubble_text);
+
+ mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container);
+ mManageSettingsView.setOnClickListener(
view -> {
showManageMenu(false /* show */);
final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
@@ -2869,10 +2874,19 @@ public class BubbleStackView extends FrameLayout
// name and icon.
if (show) {
final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
- if (bubble != null) {
+ if (bubble != null && !bubble.isAppBubble()) {
+ // Setup options for non app bubbles
+ mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation);
mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
mManageSettingsText.setText(getResources().getString(
R.string.bubbles_app_settings, bubble.getAppName()));
+ mManageSettingsView.setVisibility(VISIBLE);
+ } else {
+ // Setup options for app bubbles
+ mManageDontBubbleText.setText(R.string.bubbles_dont_bubble);
+ // App bubbles are not notification based
+ // so we don't show the option to go to notification settings
+ mManageSettingsView.setVisibility(GONE);
}
}
@@ -2937,6 +2951,15 @@ public class BubbleStackView extends FrameLayout
}
}
+ /**
+ * Checks whether manage menu notification settings action is available and visible
+ * Used for testing
+ */
+ @VisibleForTesting
+ public boolean isManageMenuSettingsVisible() {
+ return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE;
+ }
+
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index a5deac5a51da..5555bec6a28e 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
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static android.window.ScreenCapture.ScreenshotSync;
+
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.PARAMETER;
@@ -24,11 +26,13 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.hardware.HardwareBuffer;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Pair;
import android.util.SparseArray;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -125,12 +129,26 @@ public interface Bubbles {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
+ */
+ void showOrHideAppBubble(Intent intent, UserHandle user);
+
+ /** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
+ boolean isAppBubbleTaskId(int taskId);
+
+ /**
+ * @return a {@link ScreenshotSync} after performing a screenshot that may exclude the bubble
+ * layer, if one is present. The underlying {@link ScreenshotHardwareBuffer} can be access via
+ * {@link ScreenshotSync#get()} asynchronously and care should be taken to
+ * {@link HardwareBuffer#close()} the associated
+ * {@link ScreenshotHardwareBuffer#getHardwareBuffer()} when no longer required.
*/
- void showOrHideAppBubble(Intent intent);
+ ScreenshotSync getScreenshotExcludingBubble(int displayId);
/**
* @return a bubble that matches the provided shortcutId, if one exists.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index e7beeeb06534..3a3a378e00d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -64,8 +64,8 @@ class BubblesNavBarGestureTracker {
stopInternal();
- mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR,
- mContext.getDisplayId());
+ mInputMonitor = mContext.getSystemService(InputManager.class)
+ .monitorGestureInput(GESTURE_MONITOR, mContext.getDisplayId());
InputChannel inputChannel = mInputMonitor.getInputChannel();
BubblesNavBarMotionEventHandler motionEventHandler =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 7f7af935ff2b..2ea43162d225 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -16,17 +16,23 @@
package com.android.wm.shell.common;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
+import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ComponentName;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -34,19 +40,21 @@ import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -114,7 +122,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (mDisplayController.getDisplayLayout(displayId).rotation()
!= pd.mRotation && isImeShowing(displayId)) {
- pd.startAnimation(true, false /* forceRestart */);
+ pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
}
}
@@ -133,7 +141,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (pd == null) {
return false;
}
- final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+ final InsetsSource imeSource = pd.mInsetsState.peekSource(InsetsSource.ID_IME);
return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
}
@@ -209,7 +217,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
InsetsSourceControl mImeSourceControl = null;
int mAnimationDirection = DIRECTION_NONE;
ValueAnimator mAnimation = null;
@@ -237,16 +245,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return;
}
- updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
+ updateImeVisibility(insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME,
+ WindowInsets.Type.ime()));
- final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
- final Rect newFrame = newSource.getFrame();
- final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+ final InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ final Rect newFrame = newSource != null ? newSource.getFrame() : null;
+ final boolean newSourceVisible = newSource != null && newSource.isVisible();
+ final InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ final Rect oldFrame = oldSource != null ? oldSource.getFrame() : null;
mInsetsState.set(insetsState, true /* copySources */);
- if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+ if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
}
@@ -261,7 +272,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (activeControl == null) {
continue;
}
- if (activeControl.getType() == InsetsState.ITYPE_IME) {
+ if (activeControl.getType() == WindowInsets.Type.ime()) {
imeSourceControl = activeControl;
}
}
@@ -280,7 +291,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final boolean positionChanged =
!imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
} else {
if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
@@ -315,26 +326,27 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
- startAnimation(true /* show */, false /* forceRestart */);
+ startAnimation(true /* show */, false /* forceRestart */, statsToken);
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
- startAnimation(false /* show */, false /* forceRestart */);
+ startAnimation(false /* show */, false /* forceRestart */, statsToken);
}
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
// Do nothing
}
@@ -342,11 +354,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
* Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
*/
private void setVisibleDirectly(boolean visible) {
- mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
- mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible);
+ mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
+ mRequestedVisibleTypes = visible
+ ? mRequestedVisibleTypes | WindowInsets.Type.ime()
+ : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
try {
- mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId,
- mRequestedVisibilities);
+ mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
+ mRequestedVisibleTypes);
} catch (RemoteException e) {
}
}
@@ -369,9 +383,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
.navBarFrameHeight();
}
- private void startAnimation(final boolean show, final boolean forceRestart) {
- final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+ private void startAnimation(final boolean show, final boolean forceRestart,
+ @Nullable ImeTracker.Token statsToken) {
+ final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
if (imeSource == null || mImeSourceControl == null) {
+ ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
final Rect newFrame = imeSource.getFrame();
@@ -392,8 +408,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
+ (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
: (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
}
- if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
+ if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
+ ImeTracker.forLogging().onCancelled(
+ statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
boolean seek = false;
@@ -437,8 +455,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mTransactionPool.release(t);
});
mAnimation.setInterpolator(INTERPOLATOR);
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
+ @Nullable
+ private final ImeTracker.Token mStatsToken = statsToken;
@Override
public void onAnimationStart(Animator animation) {
@@ -457,8 +478,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
: 1.f;
t.setAlpha(mImeSourceControl.getLeash(), alpha);
if (mAnimationDirection == DIRECTION_SHOW) {
+ ImeTracker.forLogging().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, alpha, startY , endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
+ }
t.apply();
mTransactionPool.release(t);
}
@@ -466,6 +498,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+ Objects.toString(mImeSourceControl.getInsetsHint()));
+ }
}
@Override
@@ -478,8 +515,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
+ ImeTracker.forLogging().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(mImeSourceControl.getLeash());
removeImeSurface();
+ ImeTracker.forLogging().onHidden(mStatsToken);
+ } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
+ ImeTracker.forLogging().onShown(mStatsToken);
+ } else if (mCancelled) {
+ ImeTracker.forLogging().onCancelled(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
+ }
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
}
t.apply();
mTransactionPool.release(t);
@@ -515,16 +569,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
void removeImeSurface() {
- final IInputMethodManager imms = getImms();
- if (imms != null) {
- try {
- // Remove the IME surface to make the insets invisible for
- // non-client controlled insets.
- imms.removeImeSurface();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to remove IME surface.", e);
- }
- }
+ // Remove the IME surface to make the insets invisible for
+ // non-client controlled insets.
+ InputMethodManagerGlobal.removeImeSurface(
+ e -> Slog.e(TAG, "Failed to remove IME surface.", e));
}
/**
@@ -598,11 +646,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- public IInputMethodManager getImms() {
- return IInputMethodManager.Stub.asInterface(
- ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
- }
-
private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
if (a == b) {
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 90a01f8c5295..9bdda14cf00b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.Slog;
@@ -24,7 +25,8 @@ import android.view.IDisplayWindowInsetsController;
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import androidx.annotation.BinderThread;
@@ -156,34 +158,44 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
}
- private void showInsets(int types, boolean fromIme) {
+ private void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.showInsets(types, fromIme);
+ listener.showInsets(types, fromIme, statsToken);
}
}
- private void hideInsets(int types, boolean fromIme) {
+ private void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.forLogging().onProgress(
+ statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.hideInsets(types, fromIme);
+ listener.hideInsets(types, fromIme, statsToken);
}
}
private void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
return;
}
for (OnInsetsChangedListener listener : listeners) {
- listener.topFocusedWindowChanged(component, requestedVisibilities);
+ listener.topFocusedWindowChanged(component, requestedVisibleTypes);
}
}
@@ -192,9 +204,9 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
extends IDisplayWindowInsetsController.Stub {
@Override
public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) throws RemoteException {
+ @InsetsType int requestedVisibleTypes) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities);
+ PerDisplay.this.topFocusedWindowChanged(component, requestedVisibleTypes);
});
}
@@ -214,16 +226,18 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
@Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.showInsets(types, fromIme);
+ PerDisplay.this.showInsets(types, fromIme, statsToken);
});
}
@Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.hideInsets(types, fromIme);
+ PerDisplay.this.hideInsets(types, fromIme, statsToken);
});
}
}
@@ -239,11 +253,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
/**
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+ *
* @param component The application component that is open in the top focussed window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The {@link InsetsType} requested visible by the focused
+ * window.
*/
default void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {}
+ @InsetsType int requestedVisibleTypes) {}
/**
* Called when the window insets configuration has changed.
@@ -259,17 +275,23 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
/**
* Called when a set of insets source window should be shown by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to show
+ * @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME show request
+ * or {@code null} otherwise.
*/
- default void showInsets(int types, boolean fromIme) {}
+ default void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
/**
* Called when a set of insets source window should be hidden by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
+ * @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME hide request
+ * or {@code null} otherwise.
*/
- default void hideInsets(int types, boolean fromIme) {}
+ default void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 84840139d1a3..d6e1a82a68ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -25,7 +25,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON
import static android.util.RotationUtils.rotateBounds;
import static android.util.RotationUtils.rotateInsets;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -46,9 +45,9 @@ import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.Surface;
+import android.view.WindowInsets;
import androidx.annotation.VisibleForTesting;
@@ -372,23 +371,20 @@ public class DisplayLayout {
// Only navigation bar
if (hasNavigationBar) {
- final InsetsSource extraNavBar = insetsState.getSource(ITYPE_EXTRA_NAVIGATION_BAR);
- final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible();
+ final Insets insets = insetsState.calculateInsets(
+ insetsState.getDisplayFrame(),
+ WindowInsets.Type.navigationBars(),
+ false /* ignoreVisibility */);
+ outInsets.set(insets.left, insets.top, insets.right, insets.bottom);
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().height())
- : navBarSize;
+ outInsets.bottom = Math.max(outInsets.bottom , navBarSize);
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().width())
- : navBarSize;
+ outInsets.right = Math.max(outInsets.right , navBarSize);
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = hasExtraNav
- ? Math.max(navBarSize, extraNavBar.getFrame().width())
- : navBarSize;
+ outInsets.left = Math.max(outInsets.left , navBarSize);
}
}
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 2a1bf0ee42ba..fad3dee1f927 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.common;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
+import android.window.ScreenCapture;
import java.util.function.Consumer;
@@ -35,9 +36,9 @@ public class ScreenshotUtils {
* @param consumer Consumer for the captured buffer
*/
public static void captureLayer(SurfaceControl sc, Rect crop,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
- consumer.accept(SurfaceControl.captureLayers(
- new SurfaceControl.LayerCaptureArgs.Builder(sc)
+ Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
+ consumer.accept(ScreenCapture.captureLayers(
+ new ScreenCapture.LayerCaptureArgs.Builder(sc)
.setSourceCrop(crop)
.setCaptureSecureLayers(true)
.setAllowProtected(true)
@@ -45,7 +46,7 @@ public class ScreenshotUtils {
}
private static class BufferConsumer implements
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> {
+ Consumer<ScreenCapture.ScreenshotHardwareBuffer> {
SurfaceControl mScreenshot = null;
SurfaceControl.Transaction mTransaction;
SurfaceControl mSurfaceControl;
@@ -61,7 +62,7 @@ public class ScreenshotUtils {
}
@Override
- public void accept(SurfaceControl.ScreenshotHardwareBuffer buffer) {
+ public void accept(ScreenCapture.ScreenshotHardwareBuffer buffer) {
if (buffer == null || buffer.getHardwareBuffer() == null) {
return;
}
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 e270edb800bd..5e42782431fd 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.common;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Region;
@@ -46,6 +47,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -221,7 +223,7 @@ public class SystemWindows {
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(view.getContext(), display, wwm);
+ new SurfaceControlViewHost(view.getContext(), display, wwm, "SystemWindows");
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
@@ -304,7 +306,9 @@ public class SystemWindows {
}
}
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ @Override
+ protected SurfaceControl getParentSurface(IWindow window,
+ WindowManager.LayoutParams attrs) {
SurfaceControl leash = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName("SystemWindowLeash")
@@ -314,7 +318,7 @@ public class SystemWindows {
synchronized (this) {
mLeashForWindow.put(window.asBinder(), leash);
}
- b.setParent(leash);
+ return leash;
}
@Override
@@ -344,17 +348,17 @@ public class SystemWindows {
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration newMergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- int resizeMode) {}
+ boolean dragResizing) {}
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {}
@Override
- public void showInsets(int types, boolean fromIme) {}
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
- public void hideInsets(int types, boolean fromIme) {}
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
public void moved(int newX, int newY) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index a09aab666a31..931cf0cee28c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.tv;
+package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,36 +30,34 @@ import android.widget.RelativeLayout;
import com.android.wm.shell.R;
/**
- * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
- * (provided by the application in Pip) and media buttons.
+ * A common action button for TV window menu layouts.
*/
-public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
- private final View mButtonView;
- private OnClickListener mOnClickListener;
- public TvPipMenuActionButton(Context context) {
+ private Icon mCurrentIcon;
+
+ public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public TvPipMenuActionButton(
+ public TvWindowMenuActionButton(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_menu_action_button, this);
+ inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
- mButtonView = findViewById(R.id.button);
mButtonBackgroundView = findViewById(R.id.background);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -70,23 +70,8 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
setTextAndDescription(textResId);
}
typedArray.recycle();
- }
- @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;
- mButtonView.setOnClickListener(listener != null ? this : null);
- }
-
- @Override
- public void onClick(View v) {
- if (mOnClickListener != null) {
- // Pass the correct view - this.
- mOnClickListener.onClick(this);
- }
+ setIsCustomCloseAction(false);
}
/**
@@ -105,11 +90,24 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
}
}
+ public void setImageIconAsync(Icon icon, Handler handler) {
+ mCurrentIcon = icon;
+ // Remove old image while waiting for the new one to load.
+ mIconImageView.setImageDrawable(null);
+ icon.loadDrawableAsync(mContext, d -> {
+ // The image hasn't been set any other way and the drawable belongs to the most
+ // recently set Icon.
+ if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+ mIconImageView.setImageDrawable(d);
+ }
+ }, handler);
+ }
+
/**
* Sets the text for description the with the given string.
*/
public void setTextAndDescription(CharSequence text) {
- mButtonView.setContentDescription(text);
+ setContentDescription(text);
}
/**
@@ -119,32 +117,29 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
setTextAndDescription(getContext().getString(resId));
}
- @Override
- public void setEnabled(boolean enabled) {
- mButtonView.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mButtonView.isEnabled();
- }
-
- void setIsCustomCloseAction(boolean isCustomCloseAction) {
+ /**
+ * Marks this button as a custom close action button.
+ * This changes the style of the action button to highlight that this action finishes the
+ * Picture-in-Picture activity.
+ *
+ * @param isCustomCloseAction sets or unsets this button as a custom close action button.
+ */
+ public void setIsCustomCloseAction(boolean isCustomCloseAction) {
mIconImageView.setImageTintList(
getResources().getColorStateList(
- isCustomCloseAction ? R.color.tv_pip_menu_close_icon
- : R.color.tv_pip_menu_icon));
+ isCustomCloseAction ? R.color.tv_window_menu_close_icon
+ : R.color.tv_window_menu_icon));
mButtonBackgroundView.setBackgroundTintList(getResources()
- .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
- : R.color.tv_pip_menu_icon_bg));
+ .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg
+ : R.color.tv_window_menu_icon_bg));
}
@Override
public String toString() {
- if (mButtonView.getContentDescription() == null) {
- return TvPipMenuActionButton.class.getSimpleName();
+ if (getContentDescription() == null) {
+ return TvWindowMenuActionButton.class.getSimpleName();
}
- return mButtonView.getContentDescription().toString();
+ return getContentDescription().toString();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index c63419851f7f..69f0bad4fb45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -37,6 +37,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -58,9 +59,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
- /** The task bar expanded height. Used to determine whether to insets divider bounds or not. */
- private float mExpandedTaskBarHeight;
-
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
@@ -215,16 +213,19 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
void onInsetsChanged(InsetsState insetsState, boolean animate) {
mSplitLayout.getDividerBounds(mTempRect);
- final InsetsSource taskBarInsetsSource =
- insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
// will be drawn against task bar.
// But there is no need to do it when IME showing because there are no rounded corners at
// the bottom. This also avoids the problem of task bar height not changing when IME
// floating.
- if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)
- && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
+ if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+ for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ mTempRect.inset(source.calculateVisibleInsets(mTempRect));
+ }
+ }
}
if (!mTempRect.equals(mDividerBounds)) {
@@ -249,8 +250,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
- mExpandedTaskBarHeight = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
@@ -372,7 +371,14 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mViewHost.relayout(lp);
}
- void setInteractive(boolean interactive, String from) {
+ /**
+ * Set divider should interactive to user or not.
+ *
+ * @param interactive divider interactive.
+ * @param hideHandle divider handle hidden or not, only work when interactive is false.
+ * @param from caller from where.
+ */
+ void setInteractive(boolean interactive, boolean hideHandle, String from) {
if (interactive == mInteractive) return;
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
@@ -388,7 +394,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mMoving = false;
}
releaseTouching();
- mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
+ mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
}
private boolean isLandscape() {
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 bdf0ac2ed30c..4970fa0cb087 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
@@ -93,7 +93,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
@Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
// 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()
@@ -102,7 +102,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
.setParent(mHostLeash)
.setCallsite("SplitDecorManager#attachToParentSurface");
mIconLeash = builder.build();
- b.setParent(mIconLeash);
+ return mIconLeash;
}
/** Inflates split decor surface on the root surface. */
@@ -114,7 +114,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
null /* options */);
mHostLeash = rootLeash;
- mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this,
+ "SplitDecorManager");
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
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 ffc56b6f6106..b447a543989e 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
@@ -490,6 +490,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
/**
+ * Set divider should interactive to user or not.
+ *
+ * @param interactive divider interactive.
+ * @param hideHandle divider handle hidden or not, only work when interactive is false.
+ * @param from caller from where.
+ */
+ public void setDividerInteractive(boolean interactive, boolean hideHandle, String from) {
+ mSplitWindowManager.setInteractive(interactive, hideHandle, from);
+ }
+
+ /**
* Sets new divide position and updates bounds correspondingly. Notifies listener if the new
* target indicates dismissing split.
*/
@@ -739,21 +750,28 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
- /** Apply recorded task layout to the {@link WindowContainerTransaction}. */
- public void applyTaskChanges(WindowContainerTransaction wct,
+ /** Apply recorded task layout to the {@link WindowContainerTransaction}.
+ *
+ * @return true if stage bounds actually update.
+ */
+ public boolean applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
+ boolean boundsChanged = false;
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;
+ boundsChanged = true;
}
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;
+ boundsChanged = true;
}
+ return boundsChanged;
}
private int getSmallestWidthDp(Rect bounds) {
@@ -1095,7 +1113,7 @@ 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(!mImeShown || !mHasImeFocus,
+ setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true,
"onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
@@ -1122,7 +1140,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Restore the split layout when wm-shell is not controlling IME insets anymore.
if (!controlling && mImeShown) {
reset();
- mSplitWindowManager.setInteractive(true, "onImeControlTargetChanged");
+ setDividerInteractive(true, true, "onImeControlTargetChanged");
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
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 060ac56cae96..00361d9dd9cf 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
@@ -93,7 +93,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
}
@Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
// 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()
@@ -103,7 +103,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mParentContainerCallbacks.attachToParentSurface(builder);
mLeash = builder.build();
mParentContainerCallbacks.onLeashReady(mLeash);
- b.setParent(mLeash);
+ return mLeash;
}
/** Inflates {@link DividerView} on to the root surface. */
@@ -113,7 +113,8 @@ public final class SplitWindowManager extends WindowlessWindowManager {
"Try to inflate divider view again without release first");
}
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ "SplitWindowManager");
mDividerView = (DividerView) LayoutInflater.from(mContext)
.inflate(R.layout.split_divider, null /* root */);
@@ -126,6 +127,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
lp.token = new Binder();
lp.setTitle(mWindowName);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
}
@@ -166,9 +168,16 @@ public final class SplitWindowManager extends WindowlessWindowManager {
}
}
- void setInteractive(boolean interactive, String from) {
+ /**
+ * Set divider should interactive to user or not.
+ *
+ * @param interactive divider interactive.
+ * @param hideHandle divider handle hidden or not, only work when interactive is false.
+ * @param from caller from where.
+ */
+ void setInteractive(boolean interactive, boolean hideHandle, String from) {
if (mDividerView == null) return;
- mDividerView.setInteractive(interactive, from);
+ mDividerView.setInteractive(interactive, hideHandle, from);
}
View getDividerView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index cfb2accbcecd..b22c9c7e7529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -155,7 +155,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
}
@Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
String className = getClass().getSimpleName();
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
@@ -164,9 +164,8 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
.setCallsite(className + "#attachToParentSurface");
attachToParentSurface(builder);
mLeash = builder.build();
- b.setParent(mLeash);
-
initSurface(mLeash);
+ return mLeash;
}
protected ShellTaskOrganizer.TaskListener getTaskListener() {
@@ -364,7 +363,8 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
/** Creates a {@link SurfaceControlViewHost} for this window manager. */
@VisibleForTesting(visibility = PRIVATE)
public SurfaceControlViewHost createSurfaceViewHost() {
- return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ getClass().getSimpleName());
}
/** Gets the layout params. */
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 b0756a0afe5d..3d1ed87f1305 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -53,11 +54,11 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.Optional;
-
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides TV specific dependencies for Pip.
*/
@@ -70,7 +71,7 @@ public abstract class TvPipModule {
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -83,6 +84,7 @@ public abstract class TvPipModule {
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
+ @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(
TvPipController.create(
@@ -90,7 +92,7 @@ public abstract class TvPipModule {
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -103,6 +105,7 @@ public abstract class TvPipModule {
pipParamsChangedForwarder,
displayController,
windowManagerShellWrapper,
+ mainHandler,
mainExecutor));
}
@@ -139,14 +142,15 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new TvPipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -169,22 +173,17 @@ public abstract class TvPipModule {
Context context,
TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows,
- PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
- return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
- mainHandler);
+ return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
}
- // Handler needed for registerReceiverForAllUsers()
@WMSingleton
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder,
- TvPipBoundsState tvPipBoundsState,
- @ShellMainThread Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
return new TvPipNotificationController(context, pipMediaController,
- pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
+ pipParamsChangedForwarder);
}
@WMSingleton
@@ -206,7 +205,7 @@ public abstract class TvPipModule {
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -218,7 +217,7 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new TvPipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 15bfeb297b41..e9957fd4f4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -16,16 +16,32 @@
package com.android.wm.shell.dagger;
-import android.view.IWindowManager;
+import android.content.Context;
+import android.os.Handler;
+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.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.tv.TvSplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.tv.TvStartingWindowTypeAlgorithm;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
import dagger.Module;
import dagger.Provides;
@@ -50,5 +66,33 @@ public class TvWMShellModule {
@DynamicOverride
static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
return new TvStartingWindowTypeAlgorithm();
- };
+ }
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
+ static SplitScreenController provideSplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Handler mainHandler,
+ SystemWindows systemWindows) {
+ return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
+ shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
+ displayImeController, displayInsetsController, dragAndDropController, transitions,
+ transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
+ systemWindows);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index ef21c7e9ec0c..57b5b8f24fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -81,6 +82,7 @@ import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -98,13 +100,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.Optional;
-
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -283,21 +285,30 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static BackAnimationBackground provideBackAnimationBackground(
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler
+ @ShellBackgroundThread Handler backgroundHandler,
+ BackAnimationBackground backAnimationBackground
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context));
+ backgroundHandler, context, backAnimationBackground));
}
return Optional.empty();
}
+
//
// Bubbles (optional feature)
//
@@ -510,6 +521,9 @@ public abstract class WMShellBaseModule {
desktopModeTaskRepository, mainExecutor));
}
+ @BindsOptionalOf
+ abstract RecentsTransitionHandler optionalRecentsTransitionHandler();
+
//
// Shell transitions
//
@@ -793,6 +807,7 @@ public abstract class WMShellBaseModule {
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional,
Optional<OneHandedController> oneHandedControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
Optional<ActivityEmbeddingController> activityEmbeddingOptional,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 8f1e074ec183..cc0da2840fa0 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
@@ -21,6 +21,7 @@ import android.content.pm.LauncherApps;
import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
+import android.view.IWindowManager;
import android.view.WindowManager;
import com.android.internal.jank.InteractionJankMonitor;
@@ -65,6 +66,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -81,6 +83,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -100,15 +103,15 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -171,14 +174,15 @@ public abstract class WMShellModule {
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
null /* synchronizer */, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue);
+ taskViewTransitions, syncQueue, wmService);
}
//
@@ -343,6 +347,7 @@ public abstract class WMShellModule {
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -360,18 +365,18 @@ public abstract class WMShellModule {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, displayInsetsController,
- pipTabletopController, oneHandedController, mainExecutor));
+ pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
}
@WMSingleton
@Provides
static PipBoundsState providePipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new PipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@@ -388,8 +393,9 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
@WMSingleton
@@ -446,7 +452,7 @@ public abstract class WMShellModule {
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -458,7 +464,7 @@ public abstract class WMShellModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
@@ -477,12 +483,12 @@ public abstract class WMShellModule {
static PipTransitionController providePipTransitionController(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+ PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+ pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
splitScreenOptional);
}
@@ -523,9 +529,20 @@ public abstract class WMShellModule {
ShellInit shellInit,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsTransitionHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional);
+ pipTouchHandlerOptional, recentsTransitionHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static RecentsTransitionHandler provideRecentsTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ Optional<RecentTasksController> recentTasksController) {
+ return new RecentsTransitionHandler(shellInit, transitions,
+ recentTasksController.orElse(null));
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index bc8171058776..c9c0e40f616c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -41,6 +41,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DisplayAreaInfo;
@@ -363,10 +364,10 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
- WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
- if (wct == null) {
- wct = new WindowContainerTransaction();
- }
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> subHandler =
+ mTransitions.dispatchRequest(transition, request, this);
+ WindowContainerTransaction wct = subHandler != null
+ ? subHandler.second : new WindowContainerTransaction();
bringDesktopAppsToFront(wct);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index d094c229e0f8..998728d65e6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -129,6 +129,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+ boolean oldVisible = state.mTaskInfo.isVisible;
if (mWindowDecorViewModelOptional.isPresent()) {
mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
@@ -138,12 +139,18 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
updateRecentsForVisibleFullscreenTask(taskInfo);
final Point positionInParent = state.mTaskInfo.positionInParent;
- if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+ boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
+ boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
+
+ if (becameVisible || positionInParentChanged) {
mSyncQueue.runInSync(t -> {
if (!state.mLeash.isValid()) {
// Task vanished before sync completion
return;
}
+ if (becameVisible) {
+ t.show(state.mLeash);
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index f376e1fd6174..32894cdc5aec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -117,6 +117,7 @@ class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite("HideDisplayCutoutOrganizer.onDisplayAreaAppeared");
if (!addDisplayAreaInfoAndLeashToMap(displayAreaInfo, leash)) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 0a5cc4181caf..318a49a8de31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -39,6 +39,7 @@ import android.view.Display;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
@@ -61,7 +62,6 @@ import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
/**
@@ -140,14 +140,34 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
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))) {
+ final boolean[] navigationBarChanged = {false};
+ InsetsState.traverse(insetsState, mInsetsState, new InsetsState.OnTraverseCallbacks() {
+ @Override
+ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+ if (source1.getType() == WindowInsets.Type.navigationBars()
+ && !source1.equals(source2)) {
+ navigationBarChanged[0] = true;
+ }
+ }
+
+ @Override
+ public void onIdNotFoundInState1(int index2, InsetsSource source2) {
+ if (source2.getType() == WindowInsets.Type.navigationBars()) {
+ navigationBarChanged[0] = true;
+ }
+ }
+
+ @Override
+ public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+ if (source1.getType() == WindowInsets.Type.navigationBars()) {
+ navigationBarChanged[0] = true;
+ }
+ }
+ });
+ if (!navigationBarChanged[0]) {
return;
}
+ // Update bounds only when the insets of navigation bar or task bar is changed.
mInsetsState.set(insetsState);
updateBounds();
}
@@ -371,16 +391,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
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();
- }
+ bounds.inset(mInsetsState.calculateInsets(
+ bounds, WindowInsets.Type.navigationBars(), false /* ignoreVisibility */));
return bounds;
}
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
index b310ee2095bf..71cc8df80cad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -104,7 +104,7 @@ public final class BackgroundWindowManager extends WindowlessWindowManager {
}
@Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setColorLayer()
.setBufferSize(mDisplayBounds.width(), mDisplayBounds.height())
@@ -113,7 +113,7 @@ public final class BackgroundWindowManager extends WindowlessWindowManager {
.setName(TAG)
.setCallsite("BackgroundWindowManager#attachToParentSurface");
mLeash = builder.build();
- b.setParent(mLeash);
+ return mLeash;
}
/** Inflates background view on to the root surface. */
@@ -122,7 +122,8 @@ public final class BackgroundWindowManager extends WindowlessWindowManager {
return false;
}
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
+ "BackgroundWindowManager");
mBackgroundView = (View) LayoutInflater.from(mContext)
.inflate(R.layout.background_panel, null /* root */);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
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 451afa08040c..38ce16489b06 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
@@ -154,6 +154,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
+ leash.setUnreleasedWarningCallSite(
+ "OneHandedSiaplyAreaOrganizer.onDisplayAreaAppeared");
mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
}
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 92cf8cbf643e..9a775dff1f69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -30,7 +30,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Size;
-import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -79,6 +78,7 @@ public class PipBoundsState {
private final @NonNull Rect mExpandedBounds = new Rect();
private final @NonNull Rect mNormalMovementBounds = new Rect();
private final @NonNull Rect mExpandedMovementBounds = new Rect();
+ private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState;
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final @NonNull Context mContext;
@@ -89,8 +89,6 @@ public class PipBoundsState {
private final LauncherState mLauncherState = new LauncherState();
private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
- private int mDisplayId = Display.DEFAULT_DISPLAY;
- private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
@@ -129,10 +127,12 @@ public class PipBoundsState {
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
+ public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
}
/** Reloads the resources. */
@@ -299,31 +299,16 @@ public class PipBoundsState {
return mLastPipComponentName;
}
- /** Get the current display id. */
- public int getDisplayId() {
- return mDisplayId;
- }
-
- /** Set the current display id for the associated display layout. */
- public void setDisplayId(int displayId) {
- mDisplayId = displayId;
- }
-
/** Returns the display's bounds. */
@NonNull
public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ return mPipDisplayLayoutState.getDisplayBounds();
}
/** Get a copy of the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
- return new DisplayLayout(mDisplayLayout);
+ return mPipDisplayLayoutState.getDisplayLayout();
}
@VisibleForTesting
@@ -613,7 +598,6 @@ public class PipBoundsState {
pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
- pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
new file mode 100644
index 000000000000..0f76af48199f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Acts as a source of truth for display related information for PIP.
+ */
+@WMSingleton
+public class PipDisplayLayoutState {
+ private static final String TAG = PipDisplayLayoutState.class.getSimpleName();
+
+ private Context mContext;
+ private int mDisplayId;
+ @NonNull private DisplayLayout mDisplayLayout;
+
+ @Inject
+ public PipDisplayLayoutState(Context context) {
+ mContext = context;
+ mDisplayLayout = new DisplayLayout();
+ }
+
+ /** Update the display layout. */
+ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ /** Get a copy of the display layout. */
+ @NonNull
+ public DisplayLayout getDisplayLayout() {
+ return new DisplayLayout(mDisplayLayout);
+ }
+
+ /** Get the display bounds */
+ @NonNull
+ public Rect getDisplayBounds() {
+ return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ }
+
+ /**
+ * Apply a rotation to this layout and its parameters.
+ * @param targetRotation
+ */
+ public void rotateTo(@Surface.Rotation int targetRotation) {
+ mDisplayLayout.rotateTo(mContext.getResources(), targetRotation);
+ }
+
+ /** Get the current display id */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** Set the current display id for the associated display layout. */
+ public void setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 16f1d1c2944c..000624499f79 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
@@ -26,11 +26,14 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
+import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.wm.shell.R;
+
import java.util.List;
/**
@@ -97,16 +100,20 @@ public interface PipMenuController {
/**
* Returns a default LayoutParams for the PIP Menu.
+ * @param context the context.
* @param width the PIP stack width.
* @param height the PIP stack height.
*/
- default WindowManager.LayoutParams getPipMenuLayoutParams(String title, int width, int height) {
+ default WindowManager.LayoutParams getPipMenuLayoutParams(Context context, String title,
+ int width, int height) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
TYPE_APPLICATION_OVERLAY,
FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.setTitle(title);
+ lp.accessibilityTitle = context.getResources().getString(
+ R.string.pip_menu_accessibility_title);
return lp;
}
}
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 6831a47c3f1a..52f5a8cfd8e0 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
@@ -79,13 +79,11 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -128,7 +126,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
@@ -316,7 +314,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -332,7 +330,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -653,7 +651,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
- if (info.displayId != mPipBoundsState.getDisplayId()
+ if (info.displayId != mPipDisplayLayoutState.getDisplayId()
&& mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(info.displayId);
}
@@ -1636,15 +1634,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return animator;
}
- /** Computes destination bounds in old rotation and returns source hint rect if available. */
+ /** Computes destination bounds in old rotation and returns source hint rect if available.
+ *
+ * Note: updates the internal state of {@link PipDisplayLayoutState} by applying a rotation
+ * transformation onto the display layout.
+ */
private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
Rect outDestinationBounds, Rect sourceHintRect) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(mNextRotation);
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
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 8e7b30eb60d0..49a27c57dc73 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
@@ -40,7 +40,6 @@ 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.animation.Animator;
import android.app.ActivityManager;
@@ -65,13 +64,12 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.Optional;
@@ -85,7 +83,7 @@ public class PipTransition extends PipTransitionController {
private final Context mContext;
private final PipTransitionState mPipTransitionState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@@ -116,7 +114,7 @@ public class PipTransition extends PipTransitionController {
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -127,7 +125,7 @@ public class PipTransition extends PipTransitionController {
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -313,11 +311,7 @@ public class PipTransition extends PipTransitionController {
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
if (rotationDelta != Surface.ROTATION_0) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
wct.setBounds(mRequestedEnterTask, destinationBounds);
@@ -398,7 +392,7 @@ public class PipTransition extends PipTransitionController {
// Launcher may update the Shelf height during the animation, which will update the
// destination bounds. Because this is in fixed rotation, We need to make sure the
// finishTransaction is using the updated bounds in the display rotation.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
final Rect finishBounds = new Rect(destinationBounds);
rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
@@ -495,10 +489,11 @@ public class PipTransition extends PipTransitionController {
// Reparent the pip leash to the root with max layer so that we can animate it outside of
// parent crop, and make sure it is not covered by other windows.
final SurfaceControl pipLeash = pipChange.getLeash();
- startTransaction.reparent(pipLeash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+ startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
// Note: because of this, the bounds to animate should be translated to the root coordinate.
- final Point offset = info.getRootOffset();
+ final Point offset = info.getRoot(rootIdx).getOffset();
final Rect currentBounds = mPipBoundsState.getBounds();
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
@@ -640,7 +635,7 @@ public class PipTransition extends PipTransitionController {
@NonNull TaskInfo taskInfo) {
startTransaction.apply();
finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
- mPipBoundsState.getDisplayBounds());
+ mPipDisplayLayoutState.getDisplayBounds());
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(null, null);
}
@@ -702,7 +697,7 @@ public class PipTransition extends PipTransitionController {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change == enterPip) continue;
- if (isOpeningType(change.getMode())) {
+ if (TransitionUtil.isOpeningType(change.getMode())) {
final SurfaceControl leash = change.getLeash();
startTransaction.show(leash).setAlpha(leash, 1.f);
}
@@ -842,13 +837,9 @@ public class PipTransition extends PipTransitionController {
/** Computes destination bounds in old rotation and updates source hint rect if available. */
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
@@ -881,7 +872,7 @@ public class PipTransition extends PipTransitionController {
continue;
}
- if (isOpeningType(mode) && change.getParent() == null) {
+ if (TransitionUtil.isOpeningType(mode) && change.getParent() == null) {
final SurfaceControl leash = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
startTransaction
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 431bd7b08142..94e593b106a5 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
@@ -186,7 +186,7 @@ public class PhonePipMenuController implements PipMenuController {
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
mSplitScreenController, mPipUiEventLogger);
mSystemWindows.addView(mPipMenuView,
- getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
setShellRootAccessibilityWindow();
@@ -210,7 +210,7 @@ public class PhonePipMenuController implements PipMenuController {
@Override
public void updateMenuBounds(Rect destinationBounds) {
mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(MENU_WINDOW_TITLE, destinationBounds.width(),
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, destinationBounds.width(),
destinationBounds.height()));
updateMenuLayout(destinationBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b9525919..4a06d84ce90d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -24,11 +24,13 @@ import android.graphics.Region;
import android.os.Bundle;
import android.os.RemoteException;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
import androidx.annotation.BinderThread;
@@ -362,6 +364,15 @@ public class PipAccessibilityInteractionConnection {
}
@Override
+ public void takeScreenshotOfWindow(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+ // AbstractAccessibilityServiceConnection uses the standard
+ // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+ // so do nothing here.
+ }
+
+ @Override
public void clearAccessibilityFocus() throws RemoteException {
// Do nothing
}
@@ -370,5 +381,8 @@ public class PipAccessibilityInteractionConnection {
public void notifyOutsideTouch() throws RemoteException {
// Do nothing
}
- }
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
}
+} \ No newline at end of file
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 b59f95ce54c9..748f4a190b00 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
@@ -85,6 +85,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -142,6 +143,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
@@ -314,7 +316,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayAdded(int displayId) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -323,7 +325,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -333,7 +335,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
- if (mPipBoundsState.getDisplayId() == displayId) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
if (mEnablePipKeepClearAlgorithm) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
@@ -393,6 +395,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -416,8 +419,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
+ pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
.mImpl;
@@ -434,6 +437,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -461,6 +465,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState;
@@ -489,7 +494,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
- mPipBoundsState.setDisplayId(displayId);
+ mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
false /* saveRestoreSnapFraction */);
});
@@ -529,11 +534,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
- mPipBoundsState.setDisplayId(mContext.getDisplayId());
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -628,12 +632,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
- mDisplayInsetsController.addInsetsChangedListener(mPipBoundsState.getDisplayId(),
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
- DisplayLayout pendingLayout =
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ DisplayLayout pendingLayout = mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
if (mIsInFixedRotation
|| pendingLayout.rotation()
!= mPipBoundsState.getDisplayLayout().rotation()) {
@@ -641,8 +645,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- onDisplayChangedUncheck(
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
+ onDisplayChangedUncheck(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
if (!mEnablePipKeepClearAlgorithm) {
@@ -764,7 +768,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
- if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ if (!mPipDisplayLayoutState.getDisplayLayout().isSameGeometry(layout)) {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
@@ -778,11 +782,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
- && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+ && mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation();
// update the internal state of objects subscribed to display changes
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null;
@@ -806,11 +809,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged = mPipBoundsState.getDisplayLayout().densityDpi() != 0
- && (mPipBoundsState.getDisplayLayout().densityDpi() != layout.densityDpi());
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
final float scale = (float) layout.densityDpi()
- / mPipBoundsState.getDisplayLayout().densityDpi();
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
postChangeBounds.set(0, 0,
(int) (postChangeBounds.width() * scale),
(int) (postChangeBounds.height() * scale));
@@ -825,8 +830,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
pipSnapAlgorithm.applySnapFraction(postChangeBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(),
mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
@@ -1085,7 +1090,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
// passing to mTouchHandler/mPipTaskOrganizer
final Rect outBounds = new Rect(toBounds);
- final int rotation = mPipBoundsState.getDisplayLayout().rotation();
+ final int rotation = mPipDisplayLayoutState.getDisplayLayout().rotation();
mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
@@ -1109,11 +1114,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
// Update the display layout, note that we have to do this on every rotation even if we
// aren't in PIP since we need to update the display layout to get the right resources
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
}
/**
@@ -1126,7 +1127,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Rect outInsetBounds,
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current display
- if ((displayId != mPipBoundsState.getDisplayId()) || (fromRotation == toRotation)) {
+ if ((displayId != mPipDisplayLayoutState.getDisplayId()) || (fromRotation == toRotation)) {
return false;
}
@@ -1150,11 +1151,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Update the display layout
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1162,8 +1159,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
postChangeStackBounds, false /* adjustForIme */);
pipSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
mPipBoundsAlgorithm.getInsetBounds(outInsetBounds);
outBounds.set(postChangeStackBounds);
@@ -1181,6 +1178,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
mPipSizeSpecHandler.dump(pw, innerPrefix);
+ mPipDisplayLayoutState.dump(pw, innerPrefix);
}
/**
@@ -1288,6 +1286,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
Rect destinationBounds, SurfaceControl overlay) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> controller.stopSwipePipToHome(
taskId, componentName, destinationBounds, overlay));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index acc0caf95e35..d7d335b856be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -43,16 +43,16 @@ public class PipDoubleTapHelper {
* <p>MAX - maximum allowed screen size</p>
*/
@IntDef(value = {
- SIZE_SPEC_CUSTOM,
SIZE_SPEC_DEFAULT,
- SIZE_SPEC_MAX
+ SIZE_SPEC_MAX,
+ SIZE_SPEC_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@interface PipSizeSpec {}
- static final int SIZE_SPEC_CUSTOM = 2;
static final int SIZE_SPEC_DEFAULT = 0;
static final int SIZE_SPEC_MAX = 1;
+ static final int SIZE_SPEC_CUSTOM = 2;
/**
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
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 41ff0b35a035..fee9140d0c2c 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
@@ -228,7 +228,7 @@ public class PipResizeGestureHandler {
if (mIsEnabled) {
// Register input event receiver
- mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
"pip-resize", mDisplayId);
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index d03d075b38af..23988a62735d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -31,6 +31,7 @@ import android.util.Size;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import java.io.PrintWriter;
@@ -40,10 +41,9 @@ import java.io.PrintWriter;
public class PipSizeSpecHandler {
private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
- @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
- @VisibleForTesting
- final SizeSpecSource mSizeSpecSourceImpl;
+ private final SizeSpecSource mSizeSpecSourceImpl;
/** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
@@ -361,8 +361,9 @@ public class PipSizeSpecHandler {
}
}
- public PipSizeSpecHandler(Context context) {
+ public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
boolean enablePipSizeLargeScreen = SystemProperties
.getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
@@ -403,15 +404,9 @@ public class PipSizeSpecHandler {
mSizeSpecSourceImpl.reloadResources();
}
- /** Returns the display's bounds. */
@NonNull
- public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ private Rect getDisplayBounds() {
+ return mPipDisplayLayoutState.getDisplayBounds();
}
public Point getScreenEdgeInsets() {
@@ -423,11 +418,12 @@ public class PipSizeSpecHandler {
*/
public Rect getInsetBounds() {
Rect insetBounds = new Rect();
- Rect insets = mDisplayLayout.stableInsets();
+ DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
+ Rect insets = displayLayout.stableInsets();
insetBounds.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
- mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
- mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ displayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
return insetBounds;
}
@@ -522,8 +518,8 @@ public class PipSizeSpecHandler {
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
- pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+ pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+ pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
index 85441af9a870..5aa3c4e2abef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,2 +1,3 @@
# WM shell sub-module TV pip owner
galinap@google.com
+bronger@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 000000000000..222307fba8c2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+abstract class TvPipAction {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_FULLSCREEN,
+ ACTION_CLOSE,
+ ACTION_MOVE,
+ ACTION_EXPAND_COLLAPSE,
+ ACTION_CUSTOM,
+ ACTION_CUSTOM_CLOSE
+ })
+ public @interface ActionType {
+ }
+
+ public static final int ACTION_FULLSCREEN = 0;
+ public static final int ACTION_CLOSE = 1;
+ public static final int ACTION_MOVE = 2;
+ public static final int ACTION_EXPAND_COLLAPSE = 3;
+ public static final int ACTION_CUSTOM = 4;
+ public static final int ACTION_CUSTOM_CLOSE = 5;
+
+ @ActionType
+ private final int mActionType;
+
+ @NonNull
+ private final SystemActionsHandler mSystemActionsHandler;
+
+ TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) {
+ Objects.requireNonNull(systemActionsHandler);
+ mActionType = actionType;
+ mSystemActionsHandler = systemActionsHandler;
+ }
+
+ boolean isCloseAction() {
+ return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+ }
+
+ @ActionType
+ int getActionType() {
+ return mActionType;
+ }
+
+ abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+ abstract PendingIntent getPendingIntent();
+
+ void executeAction() {
+ mSystemActionsHandler.executeAction(mActionType);
+ }
+
+ abstract Notification.Action toNotificationAction(Context context);
+
+ interface SystemActionsHandler {
+ void executeAction(@TvPipAction.ActionType int actionType);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
new file mode 100644
index 000000000000..fa62a73ca9b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * 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 static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN;
+
+import android.annotation.NonNull;
+import android.app.RemoteAction;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+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.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates the system TvPipActions (fullscreen, close, move, expand/collapse), and handles all the
+ * changes to the actions, including the custom app actions and media actions. Other components can
+ * listen to those changes.
+ */
+public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
+ private static final String TAG = TvPipActionsProvider.class.getSimpleName();
+
+ private static final int CLOSE_ACTION_INDEX = 1;
+ private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
+
+ private final List<Listener> mListeners = new ArrayList<>();
+ private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
+
+ private final List<TvPipAction> mActionsList;
+ private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mExpandCollapseAction;
+
+ private final List<RemoteAction> mMediaActions = new ArrayList<>();
+ private final List<RemoteAction> mAppActions = new ArrayList<>();
+
+ public TvPipActionsProvider(Context context, PipMediaController pipMediaController,
+ TvPipAction.SystemActionsHandler systemActionsHandler) {
+ mSystemActionsHandler = systemActionsHandler;
+
+ mActionsList = new ArrayList<>();
+ mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
+ mSystemActionsHandler));
+
+ mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+ R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
+ mActionsList.add(mDefaultCloseAction);
+
+ mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
+
+ mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+ R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
+ mSystemActionsHandler);
+ mActionsList.add(mExpandCollapseAction);
+
+ pipMediaController.addActionListener(this::onMediaActionsChanged);
+ }
+
+ @Override
+ public void executeAction(@TvPipAction.ActionType int actionType) {
+ if (mSystemActionsHandler != null) {
+ mSystemActionsHandler.executeAction(actionType);
+ }
+ }
+
+ private void notifyActionsChanged(int added, int changed, int startIndex) {
+ for (Listener listener : mListeners) {
+ listener.onActionsChanged(added, changed, startIndex);
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
+ // Update close action.
+ mActionsList.set(CLOSE_ACTION_INDEX,
+ closeAction == null ? mDefaultCloseAction
+ : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction,
+ mSystemActionsHandler));
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);
+
+ // Replace custom actions with new ones.
+ mAppActions.clear();
+ for (RemoteAction action : appActions) {
+ if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
+ // Only show actions that aren't duplicates of the custom close action.
+ mAppActions.add(action);
+ }
+ }
+
+ updateCustomActions(mAppActions);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void onMediaActionsChanged(List<RemoteAction> actions) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMediaActionsChanged()", TAG);
+
+ mMediaActions.clear();
+ // Don't show disabled actions.
+ for (RemoteAction remoteAction : actions) {
+ if (remoteAction.isEnabled()) {
+ mMediaActions.add(remoteAction);
+ }
+ }
+
+ updateCustomActions(mMediaActions);
+ }
+
+ private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
+ List<RemoteAction> newCustomActions = customActions;
+ if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
+ // Don't show the media actions while there are app actions.
+ return;
+ } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
+ // If all the app actions were removed, show the media actions.
+ newCustomActions = mMediaActions;
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
+ int oldCustomActionsCount = 0;
+ for (TvPipAction action : mActionsList) {
+ if (action.getActionType() == ACTION_CUSTOM) {
+ oldCustomActionsCount++;
+ }
+ }
+ mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+
+ List<TvPipAction> actions = new ArrayList<>();
+ for (RemoteAction action : newCustomActions) {
+ actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler));
+ }
+ mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);
+
+ int added = newCustomActions.size() - oldCustomActionsCount;
+ int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
+ notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateExpansionEnabled(boolean enabled) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpansionState, enabled: %b", TAG, enabled);
+ int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+ boolean actionInList = actionIndex != -1;
+ if (enabled && !actionInList) {
+ mActionsList.add(mExpandCollapseAction);
+ actionIndex = mActionsList.size() - 1;
+ } else if (!enabled && actionInList) {
+ mActionsList.remove(actionIndex);
+ } else {
+ return;
+ }
+ notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void onPipExpansionToggled(boolean expanded) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
+
+ mExpandCollapseAction.update(
+ expanded ? R.string.pip_collapse : R.string.pip_expand,
+ expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+ mActionsList.indexOf(mExpandCollapseAction));
+ }
+
+ List<TvPipAction> getActionsList() {
+ return mActionsList;
+ }
+
+ @NonNull
+ TvPipAction getCloseAction() {
+ return mActionsList.get(CLOSE_ACTION_INDEX);
+ }
+
+ void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Returns the index of the first action of the given action type or -1 if none can be found.
+ */
+ int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+ for (int i = 0; i < mActionsList.size(); i++) {
+ if (mActionsList.get(i).getActionType() == actionType) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Allow components to listen to updates to the actions list, including where they happen so
+ * that changes can be animated.
+ */
+ interface Listener {
+ /**
+ * Notifies the listener how many actions were added/removed or updated.
+ *
+ * @param added can be positive (number of actions added), negative (number of actions
+ * removed) or zero (the number of actions stayed the same).
+ * @param updated the number of actions that might have been updated and need to be
+ * refreshed.
+ * @param startIndex The index of the first updated action. The added/removed actions start
+ * at (startIndex + updated).
+ */
+ void onActionsChanged(int added, int updated, int startIndex);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 22feb43ffd62..825b96921a22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -22,14 +22,12 @@ 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.Insets;
import android.graphics.Rect;
-import android.util.ArraySet;
import android.util.Size;
import android.view.Gravity;
@@ -51,11 +49,10 @@ 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;
+ @NonNull
+ private final TvPipBoundsState mTvPipBoundsState;
private int mFixedExpandedHeightInPx;
private int mFixedExpandedWidthInPx;
@@ -94,16 +91,15 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
/** 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);
- }
+ 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);
+ updateGravityOnExpansionToggled(/* expanding= */ true);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
@@ -112,10 +108,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
/** 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);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
@@ -143,25 +137,9 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
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);
- }
+ final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas();
+ final Set<Rect> unrestrictedKeepClearAreas =
+ mTvPipBoundsState.getUnrestrictedKeepClearAreas();
mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity());
mKeepClearAlgorithm.setScreenSize(screenSize);
@@ -175,165 +153,105 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
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);
- }
+ 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;
- }
+ void updateGravityOnExpansionToggled(boolean expanding) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravity, expanding: %b, fixedExpandedOrientation: %d",
+ TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation());
- 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 currentX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int currentY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+ int previousCollapsedX = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int previousCollapsedY = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.VERTICAL_GRAVITY_MASK;
- int gravityToSave = Gravity.NO_GRAVITY;
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
int updatedGravity;
-
if (expanding) {
- // save collapsed gravity
- gravityToSave = mTvPipBoundsState.getTvPipGravity();
+ // Save collapsed gravity.
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.CENTER_HORIZONTAL | (currentGravity
- & Gravity.VERTICAL_GRAVITY_MASK);
+ updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
} else {
- updatedGravity =
- Gravity.CENTER_VERTICAL | (currentGravity
- & Gravity.HORIZONTAL_GRAVITY_MASK);
+ updatedGravity = currentX | Gravity.CENTER_VERTICAL;
}
} else {
- if (previousGravity != Gravity.NO_GRAVITY) {
- // The pip hasn't been moved since expanding,
- // go back to previous collapsed position.
- updatedGravity = previousGravity;
+ // Collapse to the edge that the user moved to before.
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ updatedGravity = previousCollapsedX | currentY;
} else {
- if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
- } else {
- updatedGravity =
- Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
- }
+ updatedGravity = currentX | previousCollapsedY;
}
}
mTvPipBoundsState.setTvPipGravity(updatedGravity);
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
- }
-
- return gravityToSave;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
}
/**
- * @return true if gravity changed
+ * @return true if the gravity changed
*/
boolean updateGravity(int keycode) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateGravity, keycode: %d", TAG, keycode);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravity, keycode: %d", TAG, keycode);
- // Check if position change is valid
+ // Check if position change is valid.
if (mTvPipBoundsState.isTvPipExpanded()) {
- int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
- if (mOrientation == ORIENTATION_VERTICAL
+ int fixedOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
+ if (fixedOrientation == ORIENTATION_VERTICAL
&& (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN)
- || mOrientation == ORIENTATION_HORIZONTAL
+ || fixedOrientation == ORIENTATION_HORIZONTAL
&& (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) {
return false;
}
}
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
- int updatedGravity;
- // First axis
+ int updatedX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int updatedY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+
switch (keycode) {
case KEYCODE_DPAD_UP:
- updatedGravity = Gravity.TOP;
+ updatedY = Gravity.TOP;
break;
case KEYCODE_DPAD_DOWN:
- updatedGravity = Gravity.BOTTOM;
+ updatedY = Gravity.BOTTOM;
break;
case KEYCODE_DPAD_LEFT:
- updatedGravity = Gravity.LEFT;
+ updatedX = Gravity.LEFT;
break;
case KEYCODE_DPAD_RIGHT:
- updatedGravity = Gravity.RIGHT;
+ updatedX = Gravity.RIGHT;
break;
default:
- updatedGravity = currentGravity;
+ // NOOP - unsupported keycode
}
- // 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;
- }
+ int updatedGravity = updatedX | updatedY;
- if (updatedGravity != currentGravity) {
+ if (updatedGravity != mTvPipBoundsState.getTvPipGravity()) {
mTvPipBoundsState.setTvPipGravity(updatedGravity);
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateGravity, new gravity: %s", TAG, Gravity.toString(updatedGravity));
return true;
}
return false;
@@ -364,8 +282,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
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);
+ "%s: updateExpandedPipSize(): Expanded mode aspect ratio"
+ + " of 0 not supported", TAG);
return;
} else if (expandedRatio < 1) {
// vertical
@@ -378,16 +296,12 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Accommodate aspect ratio", TAG);
- }
+ 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);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Aspect ratio is too extreme, use max size", TAG);
expandedSize = new Size(mFixedExpandedWidthInPx, maxHeight);
}
}
@@ -401,26 +315,20 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Accommodate aspect ratio", TAG);
- }
+ 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);
- }
+ 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());
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpandedPipSize(): expanded size, width: %d, height: %d",
+ TAG, expandedSize.getWidth(), expandedSize.getHeight());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 3a6ce81821ec..8d4a38442ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -39,7 +39,6 @@ import java.util.function.Supplier;
* Manages debouncing of PiP movements and scheduling of unstashing.
*/
public class TvPipBoundsController {
- private static final boolean DEBUG = false;
private static final String TAG = "TvPipBoundsController";
/**
@@ -122,22 +121,22 @@ public class TvPipBoundsController {
cancelScheduledPlacement();
applyPlacementBounds(placement.getUnstashedBounds(), animationDuration);
} else if (immediate) {
+ boolean shouldStash = mUnstashRunnable != null || placement.getTriggerStash();
cancelScheduledPlacement();
- applyPlacementBounds(placement.getBounds(), animationDuration);
- scheduleUnstashIfNeeded(placement);
+ applyPlacement(placement, shouldStash, animationDuration);
} else {
- applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ if (mCurrentPlacementBounds != null) {
+ applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
+ }
schedulePinnedStackPlacement(placement, animationDuration);
}
}
private void schedulePinnedStackPlacement(@NonNull final Placement placement,
int animationDuration) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: schedulePinnedStackPlacement() - pip bounds: %s",
- TAG, placement.getBounds().toShortString());
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: schedulePinnedStackPlacement() - pip bounds: %s",
+ TAG, placement.getBounds().toShortString());
if (mPendingPlacement != null && Objects.equals(mPendingPlacement.getBounds(),
placement.getBounds())) {
@@ -171,30 +170,27 @@ public class TvPipBoundsController {
}
private void applyPendingPlacement() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: applyPendingPlacement()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: applyPendingPlacement()", TAG);
if (mPendingPlacement != null) {
- if (mPendingStash) {
- mPendingStash = false;
- scheduleUnstashIfNeeded(mPendingPlacement);
- }
+ applyPlacement(mPendingPlacement, mPendingStash, mPendingPlacementAnimationDuration);
+ mPendingStash = false;
+ mPendingPlacement = null;
+ }
+ }
- if (mUnstashRunnable != null) {
- // currently stashed, use stashed pos
- applyPlacementBounds(mPendingPlacement.getBounds(),
- mPendingPlacementAnimationDuration);
- } else {
- applyPlacementBounds(mPendingPlacement.getUnstashedBounds(),
- mPendingPlacementAnimationDuration);
- }
+ private void applyPlacement(@NonNull final Placement placement, boolean shouldStash,
+ int animationDuration) {
+ if (placement.getStashType() != STASH_TYPE_NONE && shouldStash) {
+ scheduleUnstashIfNeeded(placement);
}
- mPendingPlacement = null;
+ Rect bounds =
+ mUnstashRunnable != null ? placement.getBounds() : placement.getUnstashedBounds();
+ applyPlacementBounds(bounds, animationDuration);
}
- void onPipDismissed() {
+ void reset() {
mCurrentPlacementBounds = null;
mPipTargetBounds = null;
cancelScheduledPlacement();
@@ -227,10 +223,8 @@ public class TvPipBoundsController {
}
mPipTargetBounds = bounds;
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString());
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString());
if (mListener != null) {
mListener.onPipTargetBoundsChange(bounds, animationDuration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 4e3ee51326c1..e1737eccc6e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -27,9 +27,11 @@ import android.content.pm.PackageManager;
import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
+import android.view.View;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
@@ -53,25 +55,63 @@ public class TvPipBoundsState extends PipBoundsState {
public @interface Orientation {
}
- public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+ private final Context mContext;
+
+ private int mDefaultGravity;
+ private int mTvPipGravity;
+ private int mPreviousCollapsedGravity;
+ private boolean mIsRtl;
private final boolean mIsTvExpandedPipSupported;
private boolean mIsTvPipExpanded;
private boolean mTvPipManuallyCollapsed;
private float mDesiredTvExpandedAspectRatio;
- private @Orientation int mTvFixedPipOrientation;
- private int mTvPipGravity;
- private @Nullable Size mTvExpandedSize;
- private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
- private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
+ @Orientation
+ private int mTvFixedPipOrientation;
+ @Nullable
+ private Size mTvExpandedSize;
+ @NonNull
+ private Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+ @NonNull
+ private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
- super(context, pipSizeSpecHandler);
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
+ super(context, pipSizeSpecHandler, pipDisplayLayoutState);
+ mContext = context;
+ updateDefaultGravity();
+ mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
+ public int getDefaultGravity() {
+ return mDefaultGravity;
+ }
+
+ private void updateDefaultGravity() {
+ boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
+ mDefaultGravity = Gravity.BOTTOM | (isRtl ? Gravity.LEFT : Gravity.RIGHT);
+
+ if (mIsRtl != isRtl) {
+ int prevGravityX = mPreviousCollapsedGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int prevGravityY = mPreviousCollapsedGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if ((prevGravityX & Gravity.RIGHT) == Gravity.RIGHT) {
+ mPreviousCollapsedGravity = Gravity.LEFT | prevGravityY;
+ } else if ((prevGravityX & Gravity.LEFT) == Gravity.LEFT) {
+ mPreviousCollapsedGravity = Gravity.RIGHT | prevGravityY;
+ }
+ }
+ mIsRtl = isRtl;
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateDefaultGravity();
+ }
+
/**
* Initialize states when first entering PiP.
*/
@@ -88,7 +128,9 @@ public class TvPipBoundsState extends PipBoundsState {
/** Resets the TV PiP state for a new activity. */
public void resetTvPipState() {
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
- mTvPipGravity = DEFAULT_TV_GRAVITY;
+ mTvPipGravity = mDefaultGravity;
+ mPreviousCollapsedGravity = mDefaultGravity;
+ mTvPipManuallyCollapsed = false;
}
/** Set the tv expanded bounds of PiP */
@@ -103,16 +145,23 @@ public class TvPipBoundsState extends PipBoundsState {
}
/** Set the PiP aspect ratio for the expanded PiP (TV) that is desired by the app. */
- public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
+ public void setDesiredTvExpandedAspectRatio(float expandedAspectRatio, boolean override) {
if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
resetTvPipState();
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
+ if (expandedAspectRatio != 0) {
+ if (expandedAspectRatio > 1) {
+ mTvFixedPipOrientation = ORIENTATION_HORIZONTAL;
+ } else {
+ mTvFixedPipOrientation = ORIENTATION_VERTICAL;
+ }
+ }
return;
}
- if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
- || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
- || aspectRatio == 0) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
+ if ((expandedAspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
+ || (expandedAspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
+ || expandedAspectRatio == 0) {
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
}
}
@@ -124,11 +173,6 @@ public class TvPipBoundsState extends PipBoundsState {
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() {
@@ -145,6 +189,14 @@ public class TvPipBoundsState extends PipBoundsState {
return mTvPipGravity;
}
+ public void setTvPipPreviousCollapsedGravity(int gravity) {
+ mPreviousCollapsedGravity = gravity;
+ }
+
+ public int getTvPipPreviousCollapsedGravity() {
+ return mPreviousCollapsedGravity;
+ }
+
/** Sets whether the TV PiP is currently expanded. */
public void setTvPipExpanded(boolean expanded) {
mIsTvPipExpanded = expanded;
@@ -174,7 +226,8 @@ public class TvPipBoundsState extends PipBoundsState {
mPipMenuPermanentDecorInsets = permanentInsets;
}
- public @NonNull Insets getPipMenuPermanentDecorInsets() {
+ @NonNull
+ public Insets getPipMenuPermanentDecorInsets() {
return mPipMenuPermanentDecorInsets;
}
@@ -182,7 +235,8 @@ public class TvPipBoundsState extends PipBoundsState {
mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
}
- public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+ @NonNull
+ public Insets getPipMenuTemporaryDecorInsets() {
return mPipMenuTemporaryDecorInsets;
}
}
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 6bc666f074e9..d73723cc02ff 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
@@ -18,17 +18,22 @@ package com.android.wm.shell.pip.tv;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
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.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.view.Gravity;
@@ -46,11 +51,11 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -68,10 +73,9 @@ import java.util.Set;
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
- TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
- ConfigurationChangeListener, UserChangeListener {
+ DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
+ UserChangeListener {
private static final String TAG = "TvPipController";
- static final boolean DEBUG = false;
private static final int NONEXISTENT_TASK_ID = -1;
@@ -81,7 +85,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
STATE_PIP,
STATE_PIP_MENU,
})
- public @interface State {}
+ public @interface State {
+ }
/**
* State when there is no applications in Pip.
@@ -99,16 +104,28 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
*/
private static final int STATE_PIP_MENU = 2;
+ static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ static final String ACTION_CLOSE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+ static final String ACTION_MOVE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+ static final String ACTION_TOGGLE_EXPANDED_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+ static final String ACTION_TO_FULLSCREEN =
+ "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
+
private final Context mContext;
private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
+ private final TvPipActionsProvider mTvPipActionsProvider;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final PipTransitionController mPipTransitionController;
@@ -117,25 +134,27 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final DisplayController mDisplayController;
private final WindowManagerShellWrapper mWmShellWrapper;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler; // For registering the broadcast receiver
private final TvPipImpl mImpl = new TvPipImpl();
- private @State int mState = STATE_NO_PIP;
- private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
+ @State
+ private int mState = STATE_NO_PIP;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
- 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;
- private int mEduTextWindowExitAnimationDurationMs;
+ private int mEduTextWindowExitAnimationDuration;
public static Pip create(
Context context,
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -148,13 +167,14 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
return new TvPipController(
context,
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -167,6 +187,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
pipParamsChangedForwarder,
displayController,
wmShell,
+ mainHandler,
mainExecutor).mImpl;
}
@@ -175,7 +196,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -188,8 +209,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShellWrapper,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
mContext = context;
+ mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mShellController = shellController;
mDisplayController = displayController;
@@ -197,21 +220,27 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mTvPipBoundsState = tvPipBoundsState;
- mTvPipBoundsState.setDisplayLayout(layout);
- mTvPipBoundsState.setDisplayId(context.getDisplayId());
- mPipSizeSpecHandler = pipSizeSpecHandler;
- mPipSizeSpecHandler.setDisplayLayout(layout);
+
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayId(context.getDisplayId());
+
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
mPipMediaController = pipMediaController;
+ mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController,
+ this::executeAction);
mPipNotificationController = pipNotificationController;
- mPipNotificationController.setDelegate(this);
+ mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider);
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
+ mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider);
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
@@ -225,7 +254,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private void onInit() {
mPipTransitionController.registerPipTransitionCallback(this);
- loadConfigurations();
+ reloadResources();
registerPipParamsChangedListener(mPipParamsChangedForwarder);
registerTaskStackListenerCallback(mTaskStackListener);
@@ -245,22 +274,41 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
- if (isPipShown()) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: > closing Pip.", TAG);
- }
- closePip();
- }
+ int previousDefaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+ reloadResources();
- loadConfigurations();
- mPipNotificationController.onConfigurationChanged(mContext);
+ mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ mTvPipBoundsState.onConfigurationChanged();
+
+ int defaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (isPipShown() && previousDefaultGravityX != defaultGravityX) {
+ movePipToOppositeSide();
+ }
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+ mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+ mEduTextWindowExitAnimationDuration =
+ res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
+ }
+
+ private void movePipToOppositeSide() {
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToOppositeSide", TAG);
+ if ((mTvPipBoundsState.getTvPipGravity() & Gravity.RIGHT) == Gravity.RIGHT) {
+ movePip(KEYCODE_DPAD_LEFT);
+ } else if ((mTvPipBoundsState.getTvPipGravity() & Gravity.LEFT) == Gravity.LEFT) {
+ movePip(KEYCODE_DPAD_RIGHT);
+ }
}
/**
@@ -274,33 +322,32 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
* Starts the process if bringing up the Pip menu if by issuing a command to move Pip
* task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
* task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
+ *
+ * @param moveMenu If true, show the moveMenu, otherwise show the regular menu.
*/
- @Override
- public void showPictureInPictureMenu() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
- }
+ private void showPictureInPictureMenu(boolean moveMenu) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
if (mState == STATE_NO_PIP) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: > cannot open Menu from the current state.", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: > cannot open Menu from the current state.", TAG);
return;
}
setState(STATE_PIP_MENU);
- mTvPipMenuController.showMenu();
+ if (moveMenu) {
+ mTvPipMenuController.showMovementMenu();
+ } else {
+ mTvPipMenuController.showMenu();
+ }
updatePinnedStackBounds();
}
@Override
public void onMenuClosed() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: closeMenu(), state before=%s", TAG, stateToName(mState));
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closeMenu(), state before=%s", TAG, stateToName(mState));
setState(STATE_PIP);
updatePinnedStackBounds();
}
@@ -313,69 +360,40 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
/**
* Opens the "Pip-ed" Activity fullscreen.
*/
- @Override
- public void movePipToFullscreen() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
- }
+ private void movePipToFullscreen() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */);
onPipDisappeared();
}
- @Override
- public void togglePipExpansion() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: togglePipExpansion()", TAG);
- }
+ private void togglePipExpansion() {
+ 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;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(expanding);
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
- mPipNotificationController.updateExpansionState();
updatePinnedStackBounds();
}
@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);
- }
+ 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) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
boolean unrestrictedAreasChanged = !Objects.equals(unrestricted,
mTvPipBoundsState.getUnrestrictedKeepClearAreas());
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
@@ -401,34 +419,25 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
@Override
- public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) {
- mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds,
- animationDuration, rect -> mTvPipMenuController.updateExpansionState());
- mTvPipMenuController.onPipTransitionStarted(newTargetBounds);
+ public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
+ mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
+ animationDuration, null);
+ mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
}
/**
* 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);
- }
+ closeCurrentPiP(mPinnedTaskId);
+ }
- 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);
- }
+ /**
+ * Force close the current PiP after some time in case the custom action hasn't done it by
+ * itself.
+ */
+ public void customClosePip() {
+ mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
}
private void closeCurrentPiP(int pinnedTaskId) {
@@ -443,31 +452,16 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void closeEduText() {
- updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false);
+ updatePinnedStackBounds(mEduTextWindowExitAnimationDuration, false);
}
private void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
- private void checkIfPinnedTaskAppeared() {
- final TaskInfo pinnedTask = getPinnedTaskInfo();
- 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;
-
- mPipMediaController.onActivityPinned();
- mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
- }
-
private void checkIfPinnedTaskIsGone() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onTaskStackChanged()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onTaskStackChanged()", TAG);
if (isPipShown() && getPinnedTaskInfo() == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -477,71 +471,79 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private void onPipDisappeared() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
mPipNotificationController.dismiss();
+ mActionBroadcastReceiver.unregister();
+
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
- mTvPipBoundsController.onPipDismissed();
+ mTvPipBoundsController.reset();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@Override
- public void onPipTransitionStarted(int direction, Rect pipBounds) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
+ public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
+ final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+ if (enterPipTransition && mState == STATE_NO_PIP) {
+ // Set the initial ability to expand the PiP when entering PiP.
+ updateExpansionState();
}
- mTvPipMenuController.notifyPipAnimating(true);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Started(), state=%s, direction=%d",
+ TAG, stateToName(mState), direction);
}
@Override
public void onPipTransitionCanceled(int direction) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
- }
- mTvPipMenuController.notifyPipAnimating(false);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
+ mTvPipMenuController.onPipTransitionFinished(
+ PipAnimationController.isInPipDirection(direction));
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
}
@Override
public void onPipTransitionFinished(int direction) {
- if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) {
+ final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+ if (enterPipTransition && 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));
- }
- mTvPipMenuController.notifyPipAnimating(false);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipTransition_Finished(), state=%s, direction=%d",
+ TAG, stateToName(mState), direction);
+ mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
}
- private void setState(@State int state) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setState(), state=%s, prev=%s",
- TAG, stateToName(state), stateToName(mState));
- }
- mState = state;
+ private void updateExpansionState() {
+ mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
}
- private void loadConfigurations() {
- final Resources res = mContext.getResources();
- mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
- mEduTextWindowExitAnimationDurationMs =
- res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+ private void setState(@State int state) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setState(), state=%s, prev=%s",
+ TAG, stateToName(state), stateToName(mState));
+ mState = state;
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- checkIfPinnedTaskAppeared();
+ final TaskInfo pinnedTask = getPinnedTaskInfo();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onActivityPinned(), task=%s", TAG, pinnedTask);
+ if (pinnedTask == null || pinnedTask.topActivity == null) return;
+ mPinnedTaskId = pinnedTask.taskId;
+
+ mPipMediaController.onActivityPinned();
+ mActionBroadcastReceiver.register();
+ mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
+ mTvPipBoundsController.reset();
mAppOpsListener.onActivityPinned(packageName);
}
@@ -559,11 +561,8 @@ 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) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPinnedActivityRestartAttempt()", TAG);
- }
-
+ 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.
movePipToFullscreen();
@@ -578,16 +577,15 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public void onActionsChanged(List<RemoteAction> actions,
RemoteAction closeAction) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onActionsChanged()", TAG);
+ "%s: onActionsChanged()", TAG);
- mTvPipMenuController.setAppActions(actions, closeAction);
- mCloseAction = closeAction;
+ mTvPipActionsProvider.setAppActions(actions, closeAction);
}
@Override
public void onAspectRatioChanged(float ratio) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onAspectRatioChanged: %f", TAG, ratio);
+ "%s: onAspectRatioChanged: %f", TAG, ratio);
mTvPipBoundsState.setAspectRatio(ratio);
if (!mTvPipBoundsState.isTvPipExpanded()) {
@@ -598,10 +596,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onExpandedAspectRatioChanged(float ratio) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
+ "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
- mTvPipMenuController.updateExpansionState();
+ updateExpansionState();
// 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
// --> update bounds, but don't toggle
@@ -613,11 +611,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
// 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;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ false);
mTvPipBoundsState.setTvPipExpanded(false);
updatePinnedStackBounds();
}
@@ -627,11 +621,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
mTvPipBoundsAlgorithm.updateExpandedPipSize();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, true);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ true);
mTvPipBoundsState.setTvPipExpanded(true);
updatePinnedStackBounds();
}
@@ -644,11 +634,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedTaskListener() {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onImeVisibilityChanged(), visible=%b, height=%d",
- TAG, imeVisible, imeHeight);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onImeVisibilityChanged(), visible=%b, height=%d",
+ TAG, imeVisible, imeHeight);
if (imeVisible == mTvPipBoundsState.isImeShowing()
&& (!imeVisible || imeHeight == mTvPipBoundsState.getImeHeight())) {
@@ -670,17 +658,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private static TaskInfo getPinnedTaskInfo() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: getPinnedTaskInfo()", TAG);
- }
+ 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) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: taskInfo=%s", TAG, taskInfo);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: taskInfo=%s", TAG, taskInfo);
return taskInfo;
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -690,10 +674,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private static void removeTask(int taskId) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: removeTask(), taskId=%d", TAG, taskId);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removeTask(), taskId=%d", TAG, taskId);
try {
ActivityTaskManager.getService().removeTask(taskId);
} catch (Exception e) {
@@ -716,6 +698,90 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
+ private void executeAction(@TvPipAction.ActionType int actionType) {
+ switch (actionType) {
+ case TvPipAction.ACTION_FULLSCREEN:
+ movePipToFullscreen();
+ break;
+ case TvPipAction.ACTION_CLOSE:
+ closePip();
+ break;
+ case TvPipAction.ACTION_MOVE:
+ showPictureInPictureMenu(/* moveMenu= */ true);
+ break;
+ case TvPipAction.ACTION_CUSTOM_CLOSE:
+ customClosePip();
+ break;
+ case TvPipAction.ACTION_EXPAND_COLLAPSE:
+ togglePipExpansion();
+ break;
+ default:
+ // NOOP
+ break;
+ }
+ }
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+ final IntentFilter mIntentFilter;
+
+ {
+ 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);
+ mIntentFilter.addAction(ACTION_TO_FULLSCREEN);
+ }
+
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+ mMainHandler, Context.RECEIVER_NOT_EXPORTED);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+
+ if (ACTION_SHOW_PIP_MENU.equals(action)) {
+ showPictureInPictureMenu(/* moveMenu= */ false);
+ } else {
+ executeAction(getCorrespondingActionType(action));
+ }
+ }
+
+ @TvPipAction.ActionType
+ private int getCorrespondingActionType(String broadcast) {
+ if (ACTION_CLOSE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_CLOSE;
+ } else if (ACTION_MOVE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_MOVE;
+ } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_EXPAND_COLLAPSE;
+ } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) {
+ return TvPipAction.ACTION_FULLSCREEN;
+ }
+
+ // Default: handle it like an action we don't know the content of.
+ return TvPipAction.ACTION_CUSTOM;
+ }
+ }
+
private class TvPipImpl implements Pip {
// Not used
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 000000000000..49d40d3c2611
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+ private static final String TAG = TvPipCustomAction.class.getSimpleName();
+
+ private final RemoteAction mRemoteAction;
+
+ TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ Objects.requireNonNull(remoteAction);
+ mRemoteAction = remoteAction;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ if (button == null || mainHandler == null) return;
+ if (mRemoteAction.getContentDescription().length() > 0) {
+ button.setTextAndDescription(mRemoteAction.getContentDescription());
+ } else {
+ button.setTextAndDescription(mRemoteAction.getTitle());
+ }
+ button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+ button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ button.setIsCustomCloseAction(isCloseAction());
+ }
+
+ PendingIntent getPendingIntent() {
+ return mRemoteAction.getActionIntent();
+ }
+
+ void executeAction() {
+ super.executeAction();
+ try {
+ mRemoteAction.getActionIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ mRemoteAction.getIcon(),
+ mRemoteAction.getTitle(),
+ mRemoteAction.getActionIntent());
+ Bundle extras = new Bundle();
+ extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+ mRemoteAction.getContentDescription());
+ builder.addExtras(extras);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
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
index 1e54436ebce9..a94bd6ec1040 100644
--- 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
@@ -284,8 +284,10 @@ class TvPipKeepClearAlgorithm() {
): Rect? {
val movementBounds = transformedMovementBounds
val candidateEdgeRects = mutableListOf<Rect>()
+ val maxRestrictedXDistanceFraction =
+ if (isPipAnchoredToCorner()) maxRestrictedDistanceFraction else 0.0
val minRestrictedLeft =
- pipAnchorBounds.right - screenSize.width * maxRestrictedDistanceFraction
+ pipAnchorBounds.right - screenSize.width * maxRestrictedXDistanceFraction
candidateEdgeRects.add(
movementBounds.offsetCopy(movementBounds.width() + pipAreaPadding, 0)
@@ -296,7 +298,6 @@ class TvPipKeepClearAlgorithm() {
// 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()
@@ -335,8 +336,7 @@ class TvPipKeepClearAlgorithm() {
}
}
- candidateBounds.sortBy { candidateCost(it, pipAnchorBounds) }
- return candidateBounds.firstOrNull()
+ return candidateBounds.minByOrNull { candidateCost(it, pipAnchorBounds) }
}
private fun getNearbyStashedPosition(bounds: Rect, keepClearAreas: Set<Rect>): Rect {
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 4ce45e142c64..73123b153382 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
@@ -25,72 +25,52 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Insets;
-import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import android.window.SurfaceSyncGroup;
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.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.
*/
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
- private static final boolean DEBUG = TvPipController.DEBUG;
private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
private final Context mContext;
private final SystemWindows mSystemWindows;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
- private final int mPipMenuBorderWidth;
- private final int mPipEduTextShowDurationMs;
- private final int mPipEduTextHeight;
+ private TvPipActionsProvider mTvPipActionsProvider;
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
+ private boolean mMenuIsOpen;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
// Used when only showing the move menu since we want to close the menu completely when
// exiting the move menu instead of showing the regular button menu.
private boolean mCloseAfterExitMoveMenu;
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private final List<RemoteAction> mAppActions = new ArrayList<>();
- private RemoteAction mCloseAction;
-
- private SyncRtSurfaceTransactionApplier mApplier;
- private SyncRtSurfaceTransactionApplier mBackgroundApplier;
- RectF mTmpSourceRectF = new RectF();
- RectF mTmpDestinationRectF = new RectF();
- Matrix mMoveTransform = new Matrix();
-
- private final Runnable mCloseEduTextRunnable = this::closeEduText;
-
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
- SystemWindows systemWindows, PipMediaController pipMediaController,
- Handler mainHandler) {
+ SystemWindows systemWindows, Handler mainHandler) {
mContext = context;
mTvPipBoundsState = tvPipBoundsState;
mSystemWindows = systemWindows;
@@ -107,22 +87,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
mainHandler, Context.RECEIVER_EXPORTED);
-
- pipMediaController.addActionListener(this::onMediaActionsChanged);
-
- mPipEduTextShowDurationMs = context.getResources()
- .getInteger(R.integer.pip_edu_text_show_duration_ms);
- mPipEduTextHeight = context.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
- mPipMenuBorderWidth = context.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_border_width);
}
void setDelegate(Delegate delegate) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setDelegate(), delegate=%s", TAG, delegate);
- }
+ 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.");
@@ -134,6 +103,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mDelegate = delegate;
}
+ void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ }
+
@Override
public void attach(SurfaceControl leash) {
if (mDelegate == null) {
@@ -145,10 +118,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void attachPipMenu() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: attachPipMenu()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: attachPipMenu()", TAG);
if (mPipMenuView != null) {
detachPipMenu();
@@ -157,18 +128,24 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
attachPipBackgroundView();
attachPipMenuView();
- mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
- -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
- mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+ int pipEduTextHeight = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ int pipMenuBorderWidth = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+ mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
+ -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
}
private void attachPipMenuView() {
- mPipMenuView = new TvPipMenuView(mContext);
- mPipMenuView.setListener(this);
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Actions provider is not set", TAG);
+ return;
+ }
+ mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
- maybeUpdateMenuViewActions();
}
private void attachPipBackgroundView() {
@@ -180,45 +157,50 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- v.getViewRootImpl().addSurfaceChangedCallback(
- new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
});
}
private void addPipMenuViewToSystemWindows(View v, String title) {
- mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
- 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
- }
-
- void notifyPipAnimating(boolean animating) {
- mPipMenuView.setEduTextActive(!animating);
- if (!animating) {
- mPipMenuView.onPipTransitionFinished();
- }
+ mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */,
+ 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+ }
+
+ void onPipTransitionFinished(boolean enterTransition) {
+ // There is a race between when this is called and when the last frame of the pip transition
+ // is drawn. To ensure that view updates are applied only when the animation has fully drawn
+ // and the menu view has been fully remeasured and relaid out, we add a small delay here by
+ // posting on the handler.
+ mMainHandler.post(() -> {
+ if (mPipMenuView != null) {
+ mPipMenuView.onPipTransitionFinished(enterTransition);
+ }
+ });
}
- void showMovementMenuOnly() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementMenuOnly()", TAG);
- }
+ void showMovementMenu() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMovementMenuOnly()", TAG);
setInMoveMode(true);
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
+ if (mMenuIsOpen) {
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
+ } else {
+ mCloseAfterExitMoveMenu = true;
+ showMenuInternal();
+ }
}
@Override
public void showMenu() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
setInMoveMode(false);
mCloseAfterExitMoveMenu = false;
showMenuInternal();
@@ -228,46 +210,27 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) {
return;
}
- maybeCloseEduText();
- maybeUpdateMenuViewActions();
- updateExpansionState();
+ mMenuIsOpen = true;
grantPipMenuFocus(true);
if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
} else {
- mPipMenuView.showButtonsMenu();
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
}
- void onPipTransitionStarted(Rect finishBounds) {
+ void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
if (mPipMenuView != null) {
- mPipMenuView.onPipTransitionStarted(finishBounds);
- }
- }
-
- private void maybeCloseEduText() {
- if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
- mMainHandler.removeCallbacks(mCloseEduTextRunnable);
- mCloseEduTextRunnable.run();
+ mPipMenuView.onPipTransitionToTargetBoundsStarted(targetBounds);
}
}
- private void closeEduText() {
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
- mPipMenuView.hideEduText();
- mDelegate.closeEduText();
- }
-
void updateGravity(int gravity) {
- mPipMenuView.showMovementHints(gravity);
- }
-
- void updateExpansionState() {
- mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
- && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
- mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
+ if (mInMoveMode) {
+ mPipMenuView.showMovementHints(gravity);
+ }
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -275,15 +238,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
void closeMenu() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: closeMenu()", TAG);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: closeMenu()", TAG);
if (mPipMenuView == null) {
return;
}
+ mMenuIsOpen = false;
mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
@@ -297,7 +259,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mInMoveMode == moveMode) {
return;
}
-
mInMoveMode = moveMode;
if (mDelegate != null) {
mDelegate.onInMoveModeChanged();
@@ -305,32 +266,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- 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);
- }
- setInMoveMode(true);
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
- }
-
- @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) {
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- return true;
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - %b, close when exiting move menu: %b",
+ TAG, mInMoveMode, mCloseAfterExitMoveMenu);
+
if (mInMoveMode) {
setInMoveMode(false);
- mPipMenuView.showButtonsMenu();
+ if (mCloseAfterExitMoveMenu) {
+ mCloseAfterExitMoveMenu = false;
+ closeMenu();
+ } else {
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
+ }
return true;
}
return false;
@@ -338,10 +286,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public boolean onPipMovement(int keycode) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipMovement - %b", TAG, mInMoveMode);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - %b", TAG, mInMoveMode);
if (mInMoveMode) {
mDelegate.movePip(keycode);
}
@@ -351,62 +297,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void detach() {
closeMenu();
- mMainHandler.removeCallbacks(mCloseEduTextRunnable);
detachPipMenu();
mLeash = null;
}
@Override
public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAppActions()", TAG);
- }
- updateAdditionalActionsList(mAppActions, actions, closeAction);
- }
-
- private void onMediaActionsChanged(List<RemoteAction> 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, RemoteAction closeAction) {
- final int number = source != null ? source.size() : 0;
- if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
- // Nothing changed.
- return;
- }
-
- mCloseAction = closeAction;
-
- destination.clear();
- if (number > 0) {
- destination.addAll(source);
- }
- maybeUpdateMenuViewActions();
- }
-
- private void maybeUpdateMenuViewActions() {
- if (mPipMenuView == null) {
- return;
- }
- if (!mAppActions.isEmpty()) {
- mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
- } else {
- mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
- }
+ // NOOP - handled via the TvPipActionsProvider
}
@Override
@@ -419,46 +316,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
*/
@Override
public void resizePipMenu(@Nullable SurfaceControl pipLeash,
- @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()) {
+ @Nullable SurfaceControl.Transaction pipTx,
+ Rect pipBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipMenu: %s", TAG, pipBounds.toShortString());
+ if (pipBounds.isEmpty()) {
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!isMenuReadyToMove()) {
return;
}
- final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
- final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
- .withWindowCrop(menuBounds)
- .build();
-
final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
- final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
- .withWindowCrop(menuBounds)
- .build();
-
- // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
- // animations of the pip surface with the content of the front and back menu surfaces
- mBackgroundApplier.scheduleApply(backParams);
- if (pipLeash != null && t != null) {
- final SyncRtSurfaceTransactionApplier.SurfaceParams
- pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(frontParams, pipParams);
- } else {
- mApplier.scheduleApply(frontParams);
+ final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
+ if (pipTx == null) {
+ pipTx = new SurfaceControl.Transaction();
}
+ pipTx.setWindowCrop(frontSurface, menuBounds.width(), menuBounds.height());
+ pipTx.setWindowCrop(backSurface, menuBounds.width(), menuBounds.height());
+
+ // Synchronize drawing the content in the front and back surfaces together with the pip
+ // transaction and the window crop for the front and back surfaces
+ final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
+ syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
+ syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
+ updateMenuBounds(pipBounds);
+ syncGroup.addTransaction(pipTx);
+ syncGroup.markSyncReady();
}
private SurfaceControl getSurfaceControl(View v) {
@@ -466,121 +353,76 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@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());
- }
+ public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
+ Rect pipBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipMenu: %s", TAG, pipBounds.toShortString());
- if (pipDestBounds.isEmpty()) {
- if (transaction == null && DEBUG) {
+ if (pipBounds.isEmpty()) {
+ if (pipTx == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: no transaction given", TAG);
}
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!isMenuReadyToMove()) {
return;
}
- final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
- final Rect tmpSourceBounds = 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: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
- }
- mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
- } else {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: tmpSourceBounds based on menu width and height", TAG);
- }
- tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
- }
-
- mTmpSourceRectF.set(tmpSourceBounds);
- mTmpDestinationRectF.set(menuDestBounds);
- mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
-
final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
- final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
- .withMatrix(mMoveTransform)
- .build();
-
final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
- final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
- .withMatrix(mMoveTransform)
- .build();
-
- // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
- // animations of the pip surface with the content of the front and back menu surfaces
- mBackgroundApplier.scheduleApply(backParams);
- if (pipLeash != null && transaction != null) {
- final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(transaction)
- .build();
- mApplier.scheduleApply(frontParams, pipParams);
- } else {
- mApplier.scheduleApply(frontParams);
- }
-
- updateMenuBounds(pipDestBounds);
- }
-
- private boolean maybeCreateSyncApplier() {
- if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds);
+ if (pipTx == null) {
+ pipTx = new SurfaceControl.Transaction();
+ }
+ pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
+ pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+
+ // Synchronize drawing the content in the front and back surfaces together with the pip
+ // transaction and the position change for the front and back surfaces
+ final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
+ syncGroup.add(mPipMenuView.getRootSurfaceControl(), null);
+ syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null);
+ updateMenuBounds(pipBounds);
+ syncGroup.addTransaction(pipTx);
+ syncGroup.markSyncReady();
+ }
+
+ private boolean isMenuReadyToMove() {
+ final boolean ready = mPipMenuView != null && mPipMenuView.getViewRootImpl() != null
+ && mPipBackgroundView != null && mPipBackgroundView.getViewRootImpl() != null;
+ if (!ready) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not going to move PiP, either menu or its parent is not created.", TAG);
- return false;
- }
-
- if (mApplier == null) {
- mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
}
- if (mBackgroundApplier == null) {
- mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
- }
- return true;
+ return ready;
}
private void detachPipMenu() {
if (mPipMenuView != null) {
- mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
}
if (mPipBackgroundView != null) {
- mBackgroundApplier = null;
mSystemWindows.removeView(mPipBackgroundView);
mPipBackgroundView = null;
}
}
@Override
- public void updateMenuBounds(Rect destinationBounds) {
- final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
- }
+ public void updateMenuBounds(Rect pipBounds) {
+ final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
mSystemWindows.updateViewLayout(mPipBackgroundView,
- getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+ getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
-
if (mPipMenuView != null) {
- mPipMenuView.updateBounds(destinationBounds);
+ mPipMenuView.updateBounds(pipBounds);
}
}
@@ -597,43 +439,24 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onCloseButtonClick() {
- mDelegate.closePip();
- }
-
- @Override
- public void onFullscreenButtonClick() {
- mDelegate.movePipToFullscreen();
- }
-
- @Override
- public void onToggleExpandedMode() {
- mDelegate.togglePipExpansion();
+ public void onCloseEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mDelegate.closeEduText();
}
interface Delegate {
- void movePipToFullscreen();
-
void movePip(int keycode);
void onInMoveModeChanged();
- int getPipGravity();
-
- void togglePipExpansion();
-
void onMenuClosed();
void closeEduText();
-
- void closePip();
}
private void grantPipMenuFocus(boolean grantFocus) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: grantWindowFocus(%b)", TAG, grantFocus);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: grantWindowFocus(%b)", TAG, grantFocus);
try {
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
new file mode 100644
index 000000000000..6eef22562caa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -0,0 +1,280 @@
+/*
+ * 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 static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.CENTER;
+import static android.view.View.GONE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+
+import java.util.Arrays;
+
+/**
+ * The edu text drawer shows the user a hint for how to access the Picture-in-Picture menu.
+ * It displays a text in a drawer below the Picture-in-Picture window. The drawer has the same
+ * width as the Picture-in-Picture window. Depending on the Picture-in-Picture mode, there might
+ * not be enough space to fit the whole educational text in the available space. In such cases we
+ * apply a marquee animation to the TextView inside the drawer.
+ *
+ * The drawer is shown temporarily giving the user enough time to read it, after which it slides
+ * shut. We show the text for a duration calculated based on whether the text is marqueed or not.
+ */
+class TvPipMenuEduTextDrawer extends FrameLayout {
+ private static final String TAG = "TvPipMenuEduTextDrawer";
+
+ private static final float MARQUEE_DP_PER_SECOND = 30; // Copy of TextView.MARQUEE_DP_PER_SECOND
+ private static final int MARQUEE_RESTART_DELAY = 1200; // Copy of TextView.MARQUEE_DELAY
+ private final float mMarqueeAnimSpeed; // pixels per ms
+
+ private final Runnable mCloseDrawerRunnable = this::closeDrawer;
+ private final Runnable mStartScrollEduTextRunnable = this::startScrollEduText;
+
+ private final Handler mMainHandler;
+ private final Listener mListener;
+ private final TextView mEduTextView;
+
+ TvPipMenuEduTextDrawer(@NonNull Context context, Handler mainHandler, Listener listener) {
+ super(context, null, 0, 0);
+
+ mListener = listener;
+ mMainHandler = mainHandler;
+
+ // Taken from TextView.Marquee calculation
+ mMarqueeAnimSpeed =
+ (MARQUEE_DP_PER_SECOND * context.getResources().getDisplayMetrics().density) / 1000f;
+
+ mEduTextView = new TextView(mContext);
+ setupDrawer();
+ }
+
+ private void setupDrawer() {
+ final int eduTextHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.pip_menu_edu_text_view_height);
+ final int marqueeRepeatLimit = mContext.getResources()
+ .getInteger(R.integer.pip_edu_text_scroll_times);
+
+ mEduTextView.setLayoutParams(
+ new LayoutParams(MATCH_PARENT, eduTextHeight, BOTTOM | CENTER));
+ mEduTextView.setGravity(CENTER);
+ mEduTextView.setClickable(false);
+ mEduTextView.setText(createEduTextString());
+ mEduTextView.setSingleLine();
+ mEduTextView.setTextAppearance(R.style.TvPipEduText);
+ mEduTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ mEduTextView.setMarqueeRepeatLimit(marqueeRepeatLimit);
+ mEduTextView.setHorizontallyScrolling(true);
+ mEduTextView.setHorizontalFadingEdgeEnabled(true);
+ mEduTextView.setSelected(false);
+ addView(mEduTextView);
+
+ setLayoutParams(new LayoutParams(MATCH_PARENT, eduTextHeight, CENTER));
+ setClipChildren(true);
+ }
+
+ /**
+ * Initializes the edu text. Should only be called once when the PiP is entered
+ */
+ void init() {
+ ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: init()", TAG);
+ scheduleLifecycleEvents();
+ }
+
+ private void scheduleLifecycleEvents() {
+ final int startScrollDelay = mContext.getResources().getInteger(
+ R.integer.pip_edu_text_start_scroll_delay);
+ if (isEduTextMarqueed()) {
+ mMainHandler.postDelayed(mStartScrollEduTextRunnable, startScrollDelay);
+ }
+ mMainHandler.postDelayed(mCloseDrawerRunnable, startScrollDelay + getEduTextShowDuration());
+ mEduTextView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ }
+
+ @Override
+ public void onWindowDetached() {
+ mEduTextView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ mMainHandler.removeCallbacks(mStartScrollEduTextRunnable);
+ mMainHandler.removeCallbacks(mCloseDrawerRunnable);
+ }
+ });
+ }
+
+ private int getEduTextShowDuration() {
+ int eduTextShowDuration;
+ if (isEduTextMarqueed()) {
+ // Calculate the time it takes to fully scroll the text once: time = distance / speed
+ final float singleMarqueeDuration =
+ getMarqueeAnimEduTextLineWidth() / mMarqueeAnimSpeed;
+ // The TextView adds a delay between each marquee repetition. Take that into account
+ final float durationFromStartToStart = singleMarqueeDuration + MARQUEE_RESTART_DELAY;
+ // Finally, multiply by the number of times we repeat the marquee animation
+ eduTextShowDuration =
+ (int) durationFromStartToStart * mEduTextView.getMarqueeRepeatLimit();
+ } else {
+ eduTextShowDuration = mContext.getResources()
+ .getInteger(R.integer.pip_edu_text_non_scroll_show_duration);
+ }
+
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: getEduTextShowDuration(), showDuration=%d",
+ TAG, eduTextShowDuration);
+ return eduTextShowDuration;
+ }
+
+ /**
+ * Returns true if the edu text width is bigger than the width of the text view, which indicates
+ * that the edu text will be marqueed
+ */
+ private boolean isEduTextMarqueed() {
+ final int availableWidth = (int) mEduTextView.getWidth()
+ - mEduTextView.getCompoundPaddingLeft()
+ - mEduTextView.getCompoundPaddingRight();
+ return availableWidth < getEduTextWidth();
+ }
+
+ /**
+ * Returns the width of a single marquee repetition of the edu text in pixels.
+ * This is the width from the start of the edu text to the start of the next edu
+ * text when it is marqueed.
+ *
+ * This is calculated based on the TextView.Marquee#start calculations
+ */
+ private float getMarqueeAnimEduTextLineWidth() {
+ // When the TextView has a marquee animation, it puts a gap between the text end and the
+ // start of the next edu text repetition. The space is equal to a third of the TextView
+ // width
+ final float gap = mEduTextView.getWidth() / 3.0f;
+ return getEduTextWidth() + gap;
+ }
+
+ private void startScrollEduText() {
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: startScrollEduText(), repeat=%d",
+ TAG, mEduTextView.getMarqueeRepeatLimit());
+ mEduTextView.setSelected(true);
+ }
+
+ /**
+ * Returns the width of the edu text irrespective of the TextView width
+ */
+ private int getEduTextWidth() {
+ return (int) mEduTextView.getLayout().getLineWidth(0);
+ }
+
+ /**
+ * Closes the edu text drawer if it hasn't been closed yet
+ */
+ void closeIfNeeded() {
+ if (mMainHandler.hasCallbacks(mCloseDrawerRunnable)) {
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: close(), closing the edu text drawer because of user action", TAG);
+ mMainHandler.removeCallbacks(mCloseDrawerRunnable);
+ mCloseDrawerRunnable.run();
+ } else {
+ // Do nothing, the drawer has already been closed
+ }
+ }
+
+ private void closeDrawer() {
+ ProtoLog.i(WM_SHELL_PICTURE_IN_PICTURE, "%s: closeDrawer()", TAG);
+ final int eduTextFadeExitAnimationDuration = mContext.getResources().getInteger(
+ R.integer.pip_edu_text_view_exit_animation_duration);
+ final int eduTextSlideExitAnimationDuration = mContext.getResources().getInteger(
+ R.integer.pip_edu_text_window_exit_animation_duration);
+
+ // Start fading out the edu text
+ mEduTextView.animate()
+ .alpha(0f)
+ .setInterpolator(TvPipInterpolators.EXIT)
+ .setDuration(eduTextFadeExitAnimationDuration)
+ .start();
+
+ // Start animation to close the drawer by animating its height to 0
+ final ValueAnimator heightAnimation = ValueAnimator.ofInt(getHeight(), 0);
+ heightAnimation.setDuration(eduTextSlideExitAnimationDuration);
+ heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+ heightAnimation.addUpdateListener(animator -> {
+ final ViewGroup.LayoutParams params = getLayoutParams();
+ params.height = (int) animator.getAnimatedValue();
+ setLayoutParams(params);
+ if (params.height == 0) {
+ setVisibility(GONE);
+ }
+ });
+ heightAnimation.start();
+
+ mListener.onCloseEduText();
+ }
+
+ /**
+ * Creates the educational text that will be displayed to the user. Here we replace the
+ * HOME annotation in the String with an icon
+ */
+ private CharSequence createEduTextString() {
+ final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+ final SpannableString spannableString = new SpannableString(eduText);
+ Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+ .ifPresent(annotation -> {
+ final Drawable icon =
+ getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+ if (icon != null) {
+ icon.mutate();
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ spannableString.setSpan(new CenteredImageSpan(icon),
+ eduText.getSpanStart(annotation),
+ eduText.getSpanEnd(annotation),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ });
+
+ return spannableString;
+ }
+
+ /**
+ * A listener for edu text drawer event states.
+ */
+ interface Listener {
+ /**
+ * The edu text closing impacts the size of the Picture-in-Picture window and influences
+ * how it is positioned on the screen.
+ */
+ void onCloseEduText();
+ }
+
+}
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 320c05c4a415..56c602a1d4f3 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
@@ -25,205 +25,135 @@ 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.ValueAnimator;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
import android.content.Context;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.text.Annotation;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannedString;
-import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
- * 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.
+ * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
+ * the TvPipActionsProvider as well as the buttons for manually moving the PiP.
*/
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
private static final String TAG = "TvPipMenuView";
- private static final boolean DEBUG = TvPipController.DEBUG;
- private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+ private final TvPipMenuView.Listener mListener;
- @Nullable
- private Listener mListener;
+ private final TvPipActionsProvider mTvPipActionsProvider;
+
+ private final RecyclerView mActionButtonsRecyclerView;
+ private final LinearLayoutManager mButtonLayoutManager;
+ private final RecyclerViewAdapter mRecyclerViewAdapter;
- private final LinearLayout mActionButtonsContainer;
- private final View mMenuFrameView;
- private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
private final View mPipFrameView;
+ private final View mMenuFrameView;
private final View mPipView;
- private final TextView mEduTextView;
- private final View mEduTextContainerView;
+
+ private final View mPipBackground;
+ private final View mDimLayer;
+
+ private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
- private final int mEduTextFadeExitAnimationDurationMs;
- private final int mEduTextSlideExitAnimationDurationMs;
- private int mEduTextHeight;
+
+ private final int mPipMenuFadeAnimationDuration;
+ private final int mResizeAnimationDuration;
private final ImageView mArrowUp;
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
-
- private final ScrollView mScrollView;
- private final HorizontalScrollView mHorizontalScrollView;
- private View mFocusedButton;
+ private final TvWindowMenuActionButton mA11yDoneButton;
private Rect mCurrentPipBounds;
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
-
- private final TvPipMenuActionButton mExpandButton;
- private final TvPipMenuActionButton mCloseButton;
-
private boolean mSwitchingOrientation;
- private final int mPipMenuFadeAnimationDuration;
- private final int mResizeAnimationDuration;
-
- public TvPipMenuView(@NonNull Context context) {
- this(context, null);
- }
-
- public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
+ private final AccessibilityManager mA11yManager;
+ private final Handler mMainHandler;
+ public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
+ @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) {
+ super(context, null, 0, 0);
inflate(context, R.layout.tv_pip_menu, this);
- mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
- .setOnClickListener(this);
+ mMainHandler = mainHandler;
+ mListener = listener;
+ mA11yManager = context.getSystemService(AccessibilityManager.class);
- mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
- mCloseButton.setOnClickListener(this);
- mCloseButton.setIsCustomCloseAction(true);
+ mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+ mButtonLayoutManager = new LinearLayoutManager(mContext);
+ mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+ mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
- .setOnClickListener(this);
- mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
- mExpandButton.setOnClickListener(this);
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList());
+ mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
- mScrollView = findViewById(R.id.tv_pip_menu_scroll);
- mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+ tvPipActionsProvider.addListener(this);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
mPipFrameView = findViewById(R.id.tv_pip_border);
mPipView = findViewById(R.id.tv_pip);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
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);
-
- mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
- mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+ mA11yDoneButton = findViewById(R.id.tv_pip_menu_done_button);
mResizeAnimationDuration = context.getResources().getInteger(
R.integer.config_pipResizeAnimationDuration);
mPipMenuFadeAnimationDuration = context.getResources()
- .getInteger(R.integer.pip_menu_fade_animation_duration);
+ .getInteger(R.integer.tv_window_menu_fade_animation_duration);
mPipMenuOuterSpace = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
mPipMenuBorderWidth = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
- mEduTextHeight = context.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
- mEduTextFadeExitAnimationDurationMs = context.getResources()
- .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
- mEduTextSlideExitAnimationDurationMs = context.getResources()
- .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
-
- initEduText();
- }
- void initEduText() {
- final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
- final SpannableString spannableString = new SpannableString(eduText);
- Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
- .ifPresent(annotation -> {
- final Drawable icon =
- getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
- if (icon != null) {
- icon.mutate();
- icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
- spannableString.setSpan(new CenteredImageSpan(icon),
- eduText.getSpanStart(annotation),
- eduText.getSpanEnd(annotation),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- });
-
- mEduTextView.setText(spannableString);
- }
-
- void setEduTextActive(boolean active) {
- mEduTextView.setSelected(active);
+ mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, mListener);
+ ((FrameLayout) findViewById(R.id.tv_pip_menu_edu_text_drawer_placeholder))
+ .addView(mEduTextDrawer);
}
- void hideEduText() {
- final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
- heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
- heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
- heightAnimation.addUpdateListener(animator -> {
- mEduTextHeight = (int) animator.getAnimatedValue();
- });
- mEduTextView.animate()
- .alpha(0f)
- .setInterpolator(TvPipInterpolators.EXIT)
- .setDuration(mEduTextFadeExitAnimationDurationMs)
- .withEndAction(() -> {
- mEduTextContainerView.setVisibility(GONE);
- }).start();
- heightAnimation.start();
- }
+ void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+ if (targetBounds == null) {
+ return;
+ }
- void onPipTransitionStarted(Rect finishBounds) {
// Fade out content by fading in view on top.
- if (mCurrentPipBounds != null && finishBounds != null) {
+ if (mCurrentPipBounds != null) {
boolean ratioChanged = PipUtils.aspectRatioChanged(
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
- finishBounds.width() / (float) finishBounds.height());
+ targetBounds.width() / (float) targetBounds.height());
if (ratioChanged) {
- mPipView.animate()
+ mPipBackground.animate()
.alpha(1f)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
@@ -232,52 +162,54 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
// Update buttons.
- final boolean vertical = finishBounds.height() > finishBounds.width();
+ final boolean vertical = targetBounds.height() > targetBounds.width();
final boolean orientationChanged =
- vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+ vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipTransitionStarted(), orientation changed %b", TAG, orientationChanged);
+ "%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
+ TAG, orientationChanged);
if (!orientationChanged) {
return;
}
if (mButtonMenuIsVisible) {
mSwitchingOrientation = true;
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
.withEndAction(() -> {
- changeButtonScrollOrientation(finishBounds);
- updateButtonGravity(finishBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
// Only make buttons visible again in onPipTransitionFinished to keep in
// sync with PiP content alpha animation.
});
} else {
- changeButtonScrollOrientation(finishBounds);
- updateButtonGravity(finishBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
}
}
- void onPipTransitionFinished() {
+ void onPipTransitionFinished(boolean enterTransition) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionFinished()", TAG);
- // Fade in content by fading out view on top.
- mPipView.animate()
+ // Fade in content by fading out view on top (faded out at every aspect ratio change).
+ mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
.setInterpolator(TvPipInterpolators.ENTER)
.start();
- // Update buttons.
+ if (enterTransition) {
+ mEduTextDrawer.init();
+ }
+
if (mSwitchingOrientation) {
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
- } else {
- refocusPreviousButton();
}
mSwitchingOrientation = false;
}
@@ -290,111 +222,13 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
"%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
updatedBounds.height());
mCurrentPipBounds = updatedBounds;
- if (!mSwitchingOrientation) {
- updateButtonGravity(mCurrentPipBounds);
- }
-
updatePipFrameBounds();
}
- private void changeButtonScrollOrientation(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
-
- final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
- final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
- if (oldScrollView.getChildCount() == 1) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: orientation changed", TAG);
- oldScrollView.removeView(mActionButtonsContainer);
- oldScrollView.setVisibility(GONE);
- mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
- : LinearLayout.HORIZONTAL);
- newScrollView.addView(mActionButtonsContainer);
- newScrollView.setVisibility(VISIBLE);
- if (mFocusedButton != null) {
- mFocusedButton.requestFocus();
- }
- }
- }
-
- /**
- * Change button gravity based on new dimensions
- */
- private void updateButtonGravity(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
- // Use Math.max since the possible orientation change might not have been applied yet.
- final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
- mActionButtonsContainer.getWidth());
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: buttons container width: %s, height: %s", TAG,
- mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
- final boolean buttonsFit =
- vertical ? buttonsSize < bounds.height()
- : buttonsSize < bounds.width();
- final int buttonGravity = buttonsFit ? Gravity.CENTER
- : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
- final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
- params.gravity = buttonGravity;
- mActionButtonsContainer.setLayoutParams(params);
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
- Gravity.toString(buttonGravity));
- }
-
- private void refocusPreviousButton() {
- if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
- return;
- }
- final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
- if (!mFocusedButton.hasFocus()) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: request focus from: %s", TAG, mFocusedButton);
- mFocusedButton.requestFocus();
- } else {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: already focused: %s", TAG, mFocusedButton);
- }
-
- // Do we need to scroll?
- final Rect buttonBounds = new Rect();
- final Rect scrollBounds = new Rect();
- if (vertical) {
- mScrollView.getDrawingRect(scrollBounds);
- } else {
- mHorizontalScrollView.getDrawingRect(scrollBounds);
- }
- mFocusedButton.getHitRect(buttonBounds);
-
- if (scrollBounds.contains(buttonBounds)) {
- // Button is already completely visible, don't scroll
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: not scrolling", TAG);
- return;
- }
-
- // Scrolling so the button is visible to the user.
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrolling to focused button", TAG);
-
- if (vertical) {
- mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- } else {
- mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- }
- }
-
Rect getPipMenuContainerBounds(Rect pipBounds) {
final Rect menuUiBounds = new Rect(pipBounds);
menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
- menuUiBounds.bottom += mEduTextHeight;
+ menuUiBounds.bottom += mEduTextDrawer.getHeight();
return menuUiBounds;
}
@@ -420,78 +254,78 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mPipView.setLayoutParams(pipViewParams);
}
-
- }
-
- void setListener(@Nullable Listener listener) {
- mListener = listener;
- }
-
- void setExpandedModeEnabled(boolean enabled) {
- mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
- }
-
- void setIsExpanded(boolean expanded) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setIsExpanded, expanded: %b", TAG, expanded);
+ // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+ // the button would lose focus which would cause a need for scrolling and re-focusing after
+ // the animation finishes, which does not look good.
+ View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ mActionButtonsRecyclerView.scrollToPosition(
+ mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
}
- mExpandButton.setImageResource(
- expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
- mExpandButton.setTextAndDescription(
- expanded ? R.string.pip_collapse : R.string.pip_expand);
}
/**
* @param gravity for the arrow hints
*/
void showMoveMenu(int gravity) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- }
- mButtonMenuIsVisible = false;
- mMoveMenuIsVisible = true;
- showButtonsMenu(false);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
showMovementHints(gravity);
+ setMenuButtonsVisible(false);
setFrameHighlighted(true);
+
+ animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
+
+ mEduTextDrawer.closeIfNeeded();
}
- void showButtonsMenu() {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu()", TAG);
- }
- mButtonMenuIsVisible = true;
- mMoveMenuIsVisible = false;
- showButtonsMenu(true);
+ void showButtonsMenu(boolean exitingMoveMode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
+ animateAlphaTo(1f, mDimLayer);
+ mEduTextDrawer.closeIfNeeded();
- // Always focus on the first button when opening the menu, except directly after moving.
- if (mFocusedButton == null) {
- // Focus on first button (there is a Space at position 0)
- mFocusedButton = mActionButtonsContainer.getChildAt(1);
- // Reset scroll position.
- mScrollView.scrollTo(0, 0);
- mHorizontalScrollView.scrollTo(
- isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+ if (exitingMoveMode) {
+ scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
+ /* alwaysScroll= */ false);
+ } else {
+ scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ }
+ }
+
+ private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: scrollAndRefocusButton, target: %d", TAG, position);
+
+ if (alwaysScroll || !refocusButton(position)) {
+ mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(position));
}
- refocusPreviousButton();
}
/**
- * Hides all menu views, including the menu frame.
+ * @return true if focus was requested, false if focus request could not be carried out due to
+ * the view for the position not being available (scrolling beforehand will be necessary).
*/
+ private boolean refocusButton(int position) {
+ View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+ if (itemToFocus != null) {
+ itemToFocus.requestFocus();
+ itemToFocus.requestAccessibilityFocus();
+ }
+ return itemToFocus != null;
+ }
+
void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
- mFocusedButton = null;
- mButtonMenuIsVisible = false;
- mMoveMenuIsVisible = false;
- showButtonsMenu(false);
+ setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
+ animateAlphaTo(0f, mDimLayer);
}
@Override
@@ -522,143 +356,30 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
});
}
- /**
- * Button order:
- * - Fullscreen
- * - Close
- * - Custom actions (app or media actions)
- * - System actions
- */
- 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) {
- // Add buttons until we have enough to display all the actions.
- while (actionsNumber > buttonsNumber) {
- TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
- button.setOnClickListener(this);
-
- mActionButtonsContainer.addView(button,
- FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
- mAdditionalButtons.add(button);
-
- buttonsNumber++;
- }
- } else if (actionsNumber < buttonsNumber) {
- // Hide buttons until we as many as the actions.
- while (actionsNumber < buttonsNumber) {
- final View button = mAdditionalButtons.get(buttonsNumber - 1);
- button.setVisibility(View.GONE);
- button.setTag(null);
-
- buttonsNumber--;
- }
- }
-
- // "Assign" actions to the buttons.
- for (int index = 0; index < actionsNumber; index++) {
- final RemoteAction action = actions.get(index);
- final TvPipMenuActionButton button = mAdditionalButtons.get(index);
-
- // Remove action if it matches the custom close action.
- if (PipUtils.remoteActionsMatch(action, closeAction)) {
- button.setVisibility(GONE);
- continue;
- }
- setActionForButton(action, button, mainHandler);
- }
-
- if (mCurrentPipBounds != null) {
- updateButtonGravity(mCurrentPipBounds);
- refocusPreviousButton();
- }
- }
-
- private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
- Handler mainHandler) {
- button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- if (action.getContentDescription().length() > 0) {
- button.setTextAndDescription(action.getContentDescription());
- } else {
- button.setTextAndDescription(action.getTitle());
- }
- button.setEnabled(action.isEnabled());
- button.setTag(action);
- action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
- }
-
- @Nullable
- SurfaceControl getWindowSurfaceControl() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- }
- final SurfaceControl out = root.getSurfaceControl();
- if (out != null && out.isValid()) {
- return out;
- }
- return null;
- }
-
@Override
- public void onClick(View v) {
- if (mListener == null) return;
-
- 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();
- if (action != null) {
- try {
- action.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send action, %s", TAG, e);
- }
- } else {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: RemoteAction is null", TAG);
- }
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated);
+ if (added > 0) {
+ mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added);
+ } else if (added < 0) {
+ mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (mListener != null && event.getAction() == ACTION_UP) {
- if (!mMoveMenuIsVisible) {
- mFocusedButton = mActionButtonsContainer.getFocusedChild();
+ if (event.getAction() == ACTION_UP) {
+
+ if (event.getKeyCode() == KEYCODE_BACK) {
+ mListener.onBackPress();
+ return true;
+ }
+
+ if (mA11yManager.isEnabled()) {
+ return super.dispatchKeyEvent(event);
}
switch (event.getKeyCode()) {
- case KEYCODE_BACK:
- mListener.onBackPress();
- return true;
case KEYCODE_DPAD_UP:
case KEYCODE_DPAD_DOWN:
case KEYCODE_DPAD_LEFT:
@@ -679,15 +400,39 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
* 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));
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
+ mMoveMenuIsVisible = true;
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
+
+ boolean a11yEnabled = mA11yManager.isEnabled();
+ setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP);
+ setArrowA11yEnabled(mArrowDown, a11yEnabled, KEYCODE_DPAD_DOWN);
+ setArrowA11yEnabled(mArrowLeft, a11yEnabled, KEYCODE_DPAD_LEFT);
+ setArrowA11yEnabled(mArrowRight, a11yEnabled, KEYCODE_DPAD_RIGHT);
+
+ animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
+ if (a11yEnabled) {
+ mA11yDoneButton.setVisibility(VISIBLE);
+ mA11yDoneButton.setOnClickListener(v -> {
+ mListener.onExitMoveMode();
+ });
+ mA11yDoneButton.requestFocus();
+ mA11yDoneButton.requestAccessibilityFocus();
+ }
+ }
+
+ private void setArrowA11yEnabled(View arrowView, boolean enabled, int keycode) {
+ arrowView.setClickable(enabled);
+ if (enabled) {
+ arrowView.setOnClickListener(v -> {
+ mListener.onPipMovement(keycode);
+ });
+ }
}
private boolean checkGravity(int gravity, int feature) {
@@ -698,40 +443,84 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
* 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);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMovementHints()", TAG);
+
+ if (!mMoveMenuIsVisible) {
+ return;
}
+ mMoveMenuIsVisible = false;
+
animateAlphaTo(0, mArrowUp);
animateAlphaTo(0, mArrowRight);
animateAlphaTo(0, mArrowDown);
animateAlphaTo(0, mArrowLeft);
+ animateAlphaTo(0, mA11yDoneButton);
}
/**
* Show or hide the pip buttons menu.
*/
- public void showButtonsMenu(boolean show) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showUserActions: %b", TAG, show);
- }
- if (show) {
- mActionButtonsContainer.setVisibility(VISIBLE);
- refocusPreviousButton();
- }
- animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+ private void setMenuButtonsVisible(boolean visible) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showUserActions: %b", TAG, visible);
+ mButtonMenuIsVisible = visible;
+ animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
private void setFrameHighlighted(boolean highlighted) {
mMenuFrameView.setActivated(highlighted);
}
- interface Listener {
+ private class RecyclerViewAdapter extends
+ RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> {
- void onBackPress();
+ private final List<TvPipAction> mActionList;
- void onEnterMoveMode();
+ RecyclerViewAdapter(List<TvPipAction> actionList) {
+ this.mActionList = actionList;
+ }
+
+ @NonNull
+ @Override
+ public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+ TvPipAction action = mActionList.get(position);
+ action.populateButton(holder.mButton, mMainHandler);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mActionList.size();
+ }
+
+ private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+ TvWindowMenuActionButton mButton;
+
+ ButtonViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mButton = (TvWindowMenuActionButton) itemView;
+ mButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ TvPipAction action = mActionList.get(
+ mActionButtonsRecyclerView.getChildLayoutPosition(v));
+ if (action != null) {
+ action.executeAction();
+ }
+ }
+ }
+ }
+
+ interface Listener extends TvPipMenuEduTextDrawer.Listener {
+
+ void onBackPress();
/**
* Called when a button for exiting move mode was pressed.
@@ -745,11 +534,5 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
* @return whether pip movement was handled.
*/
boolean onPipMovement(int keycode);
-
- void onCloseButtonClick();
-
- void onFullscreenButtonClick();
-
- void onToggleExpandedMode();
}
-}
+} \ No newline at end of file
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 61a609d9755e..f22ee595e6c9 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
@@ -16,18 +16,13 @@
package com.android.wm.shell.pip.tv;
-import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
-import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
-
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,7 +30,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
-import android.os.Handler;
import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +41,6 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -55,39 +48,18 @@ import java.util.List;
* <p>Once it's created, it will manage the PiP notification UI by itself except for handling
* configuration changes and user initiated expanded PiP toggling.
*/
-public class TvPipNotificationController {
- private static final String TAG = "TvPipNotification";
+public class TvPipNotificationController implements TvPipActionsProvider.Listener {
+ private static final String TAG = TvPipNotificationController.class.getSimpleName();
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
- private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
- private static final String ACTION_SHOW_PIP_MENU =
- "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 static final String ACTION_FULLSCREEN =
- "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
private final Context mContext;
private final PackageManager mPackageManager;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
- private final ActionBroadcastReceiver mActionBroadcastReceiver;
- private final Handler mMainHandler;
- private Delegate mDelegate;
- private final TvPipBoundsState mTvPipBoundsState;
-
- private String mDefaultTitle;
-
- private final List<RemoteAction> mCustomActions = new ArrayList<>();
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private RemoteAction mCustomCloseAction;
+ private TvPipActionsProvider mTvPipActionsProvider;
private MediaSession.Token mMediaSessionToken;
@@ -95,55 +67,41 @@ public class TvPipNotificationController {
private String mPackageName;
private boolean mIsNotificationShown;
+ private String mDefaultTitle;
private String mPipTitle;
private String mPipSubtitle;
+ // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes.
+ @NonNull
+ private Notification.Action[] mPipActions;
+
private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
- Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
- mMainHandler = mainHandler;
- mTvPipBoundsState = tvPipBoundsState;
+
+ mPipActions = new Notification.Action[0];
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
.setOngoing(true)
.setCategory(Notification.CATEGORY_SYSTEM)
.setShowWhen(true)
+ .setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.pip_icon)
.setAllowSystemGeneratedContextualActions(false)
- .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
- .setDeleteIntent(getCloseAction().actionIntent)
- .extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
-
- mActionBroadcastReceiver = new ActionBroadcastReceiver();
+ .setContentIntent(
+ createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN));
+ // TvExtender and DeleteIntent set later since they might change.
- pipMediaController.addActionListener(this::onMediaActionsChanged);
pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
pipParamsChangedForwarder.addListener(
new PipParamsChangedForwarder.PipParamsChangedCallback() {
@Override
- public void onExpandedAspectRatioChanged(float ratio) {
- updateExpansionState();
- }
-
- @Override
- public void onActionsChanged(List<RemoteAction> actions,
- RemoteAction closeAction) {
- mCustomActions.clear();
- mCustomActions.addAll(actions);
- mCustomCloseAction = closeAction;
- updateNotificationContent();
- }
-
- @Override
public void onTitleChanged(String title) {
mPipTitle = title;
updateNotificationContent();
@@ -156,34 +114,33 @@ public class TvPipNotificationController {
}
});
- onConfigurationChanged(context);
+ onConfigurationChanged();
}
- void setDelegate(Delegate delegate) {
- 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.");
- }
- if (delegate == null) {
- throw new IllegalArgumentException("The delegate must not be null.");
- }
+ /**
+ * Call before showing any notification.
+ */
+ void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mTvPipActionsProvider.addListener(this);
+ }
- mDelegate = delegate;
+ void onConfigurationChanged() {
+ mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title);
+ updateNotificationContent();
}
void show(String packageName) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
- if (mDelegate == null) {
- throw new IllegalStateException("Delegate is not set.");
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Missing TvPipActionsProvider", TAG);
+ return;
}
mIsNotificationShown = true;
mPackageName = packageName;
mActivityIcon = getActivityIcon();
- mActionBroadcastReceiver.register();
updateNotificationContent();
}
@@ -193,151 +150,42 @@ public class TvPipNotificationController {
mIsNotificationShown = false;
mPackageName = null;
- mActionBroadcastReceiver.unregister();
-
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
}
- private Notification.Action getToggleAction(boolean expanded) {
- if (expanded) {
- return createSystemAction(R.drawable.pip_ic_collapse,
- R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
- } else {
- return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
- ACTION_TOGGLE_EXPANDED_PIP);
- }
- }
-
- private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
- Notification.Action.Builder builder = new Notification.Action.Builder(
- Icon.createWithResource(mContext, iconRes),
- mContext.getString(titleRes),
- createPendingIntent(mContext, action));
- builder.setContextual(true);
- return builder.build();
- }
-
- private void onMediaActionsChanged(List<RemoteAction> actions) {
- mMediaActions.clear();
- mMediaActions.addAll(actions);
- if (mCustomActions.isEmpty()) {
- updateNotificationContent();
- }
- }
-
private void onMediaSessionTokenChanged(MediaSession.Token token) {
mMediaSessionToken = token;
updateNotificationContent();
}
- private Notification.Action remoteToNotificationAction(RemoteAction action) {
- return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
- }
-
- private Notification.Action remoteToNotificationAction(RemoteAction action,
- int semanticAction) {
- Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
- action.getTitle(),
- action.getActionIntent());
- if (action.getContentDescription() != null) {
- Bundle extras = new Bundle();
- extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
- action.getContentDescription());
- builder.addExtras(extras);
- }
- builder.setSemanticAction(semanticAction);
- builder.setContextual(true);
- return builder.build();
- }
-
- private Notification.Action[] getNotificationActions() {
- final List<Notification.Action> actions = new ArrayList<>();
-
- // 1. Fullscreen
- actions.add(getFullscreenAction());
- // 2. Close
- actions.add(getCloseAction());
- // 3. App actions
- final List<RemoteAction> appActions =
- mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
- for (RemoteAction appAction : appActions) {
- if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
- || !appAction.isEnabled()) {
- continue;
- }
- actions.add(remoteToNotificationAction(appAction));
- }
- // 4. Move
- actions.add(getMoveAction());
- // 5. Toggle expansion (if expanded PiP enabled)
- if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
- && mTvPipBoundsState.isTvExpandedPipSupported()) {
- actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
- }
- return actions.toArray(new Notification.Action[0]);
- }
-
- private Notification.Action getCloseAction() {
- if (mCustomCloseAction == null) {
- return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
- ACTION_CLOSE_PIP);
- } else {
- return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
- }
- }
-
- private Notification.Action getFullscreenAction() {
- return createSystemAction(R.drawable.pip_ic_fullscreen_white,
- R.string.pip_fullscreen, ACTION_FULLSCREEN);
- }
-
- private Notification.Action getMoveAction() {
- return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
- ACTION_MOVE_PIP);
- }
-
- /**
- * Called by {@link TvPipController} when the configuration is changed.
- */
- void onConfigurationChanged(Context context) {
- mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
- updateNotificationContent();
- }
-
- void updateExpansionState() {
- updateNotificationContent();
- }
-
private void updateNotificationContent() {
if (mPackageManager == null || !mIsNotificationShown) {
return;
}
- Notification.Action[] actions = getNotificationActions();
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
- getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
- for (Notification.Action action : actions) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
- action.toString());
- }
-
+ getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length);
mNotificationBuilder
.setWhen(System.currentTimeMillis())
.setContentTitle(getNotificationTitle())
.setContentText(mPipSubtitle)
.setSubText(getApplicationLabel(mPackageName))
- .setActions(actions);
+ .setActions(mPipActions);
setPipIcon();
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
mNotificationBuilder.setExtras(extras);
+ PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
+ mNotificationBuilder.setDeleteIntent(closeIntent);
// TvExtender not recognized if not set last.
mNotificationBuilder.extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
+ .setContentIntent(
+ createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU))
+ .setDeleteIntent(closeIntent));
+
mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
mNotificationBuilder.build());
}
@@ -389,68 +237,20 @@ public class TvPipNotificationController {
return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
}
- private static PendingIntent createPendingIntent(Context context, String action) {
+ static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
- private class ActionBroadcastReceiver extends BroadcastReceiver {
- final IntentFilter mIntentFilter;
- {
- 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);
- mIntentFilter.addAction(ACTION_FULLSCREEN);
- }
- boolean mRegistered = false;
-
- void register() {
- if (mRegistered) return;
-
- mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
- mMainHandler);
- mRegistered = true;
- }
-
- void unregister() {
- if (!mRegistered) return;
-
- mContext.unregisterReceiver(this);
- mRegistered = false;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- 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();
- } else if (ACTION_FULLSCREEN.equals(action)) {
- mDelegate.movePipToFullscreen();
- }
+ @Override
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ List<TvPipAction> actions = mTvPipActionsProvider.getActionsList();
+ mPipActions = new Notification.Action[actions.size()];
+ for (int i = 0; i < mPipActions.length; i++) {
+ mPipActions[i] = actions.get(i).toNotificationAction(mContext);
}
+ updateNotificationContent();
}
- interface Delegate {
- void showPictureInPictureMenu();
-
- void closePip();
-
- void enterPipMovementMenu();
-
- void togglePipExpansion();
-
- void movePipToFullscreen();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 000000000000..4b82e4bdb64a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+ @StringRes
+ private int mTitleResource;
+ @DrawableRes
+ private int mIconResource;
+
+ private final PendingIntent mBroadcastIntent;
+
+ TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon,
+ String broadcastAction, @NonNull Context context,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ update(title, icon);
+ mBroadcastIntent = TvPipNotificationController.createPendingIntent(context,
+ broadcastAction);
+ }
+
+ void update(@StringRes int title, @DrawableRes int icon) {
+ mTitleResource = title;
+ mIconResource = icon;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ button.setTextAndDescription(mTitleResource);
+ button.setImageResource(mIconResource);
+ button.setEnabled(true);
+ button.setIsCustomCloseAction(false);
+ }
+
+ PendingIntent getPendingIntent() {
+ return mBroadcastIntent;
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ Icon.createWithResource(context, mIconResource),
+ context.getString(mTitleResource),
+ mBroadcastIntent);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index be9b9361b359..f6856f15f16f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -36,7 +37,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
@@ -51,7 +51,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -63,8 +63,8 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor mainExecutor) {
- super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
- boundsHandler, pipMenuController, pipAnimationController,
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
+ pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 1a6c1d65db03..4048c5b8feab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -17,6 +17,11 @@
package com.android.wm.shell.recents;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -45,4 +50,10 @@ interface IRecentTasks {
* Gets the set of running tasks.
*/
RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+
+ /**
+ * Starts a recents transition.
+ */
+ oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) = 5;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0d9faa3c6f83..c5bfd8753994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,13 +24,18 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RE
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import android.view.IRecentsAnimationRunner;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -79,6 +84,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
+ private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -150,6 +156,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
}
+ void setTransitionHandler(RecentsTransitionHandler handler) {
+ mTransitionHandler = handler;
+ }
+
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
@@ -492,5 +502,18 @@ public class RecentTasksController implements TaskStackListenerCallback,
true /* blocking */);
return tasks[0];
}
+
+ @Override
+ public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ if (mController.mTransitionHandler == null) {
+ Slog.e(TAG, "Used shell-transitions startRecentsTransition without"
+ + " shell-transitions");
+ return;
+ }
+ executeRemoteCallWithTaskPermission(mController, "startRecentsTransition",
+ (controller) -> controller.mTransitionHandler.startRecentsTransition(
+ intent, fillIn, options, appThread, listener));
+ }
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
new file mode 100644
index 000000000000..da8c805eb038
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
+ * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
+ */
+public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "RecentsTransitionHandler";
+
+ private final Transitions mTransitions;
+ private final ShellExecutor mExecutor;
+ private IApplicationThread mAnimApp = null;
+ private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+
+ /**
+ * List of other handlers which might need to mix recents with other things. These are checked
+ * in the order they are added. Ideally there should only be one.
+ */
+ private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+
+ public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ @Nullable RecentTasksController recentTasksController) {
+ mTransitions = transitions;
+ mExecutor = transitions.getMainExecutor();
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (recentTasksController == null) return;
+ shellInit.addInitCallback(() -> {
+ recentTasksController.setTransitionHandler(this);
+ transitions.addHandler(this);
+ }, this);
+ }
+
+ /** Register a mixer handler. {@see RecentsMixedHandler}*/
+ public void addMixer(RecentsMixedHandler mixer) {
+ mMixers.add(mixer);
+ }
+
+ /** Unregister a Mixed Handler */
+ public void removeMixer(RecentsMixedHandler mixer) {
+ mMixers.remove(mixer);
+ }
+
+ void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.sendPendingIntent(intent, fillIn, options);
+ final RecentsController controller = new RecentsController(listener);
+ RecentsMixedHandler mixer = null;
+ Transitions.TransitionHandler mixedHandler = null;
+ for (int i = 0; i < mMixers.size(); ++i) {
+ mixedHandler = mMixers.get(i).handleRecentsRequest(wct);
+ if (mixedHandler != null) {
+ mixer = mMixers.get(i);
+ break;
+ }
+ }
+ final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
+ mixedHandler == null ? this : mixedHandler);
+ if (mixer != null) {
+ mixer.setRecentsTransition(transition);
+ }
+ if (transition == null) {
+ controller.cancel();
+ return;
+ }
+ controller.setTransition(transition);
+ mControllers.add(controller);
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(IBinder transition,
+ TransitionRequestInfo request) {
+ // do not directly handle requests. Only entry point should be via startRecentsTransition
+ return null;
+ }
+
+ private int findController(IBinder transition) {
+ for (int i = mControllers.size() - 1; i >= 0; --i) {
+ if (mControllers.get(i).mTransition == transition) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int controllerIdx = findController(transition);
+ if (controllerIdx < 0) return false;
+ final RecentsController controller = mControllers.get(controllerIdx);
+ Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+ mAnimApp = null;
+ if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ Transitions.TransitionFinishCallback finishCallback) {
+ final int targetIdx = findController(mergeTarget);
+ if (targetIdx < 0) return;
+ final RecentsController controller = mControllers.get(targetIdx);
+ controller.merge(info, t, finishCallback);
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted,
+ SurfaceControl.Transaction finishTransaction) {
+ final int idx = findController(transition);
+ if (idx < 0) return;
+ mControllers.get(idx).cancel();
+ }
+
+ /** There is only one of these and it gets reset on finish. */
+ private class RecentsController extends IRecentsAnimationController.Stub {
+ private IRecentsAnimationRunner mListener;
+ private IBinder.DeathRecipient mDeathHandler;
+ private Transitions.TransitionFinishCallback mFinishCB = null;
+ private SurfaceControl.Transaction mFinishTransaction = null;
+
+ /**
+ * List of tasks that we are switching away from via this transition. Upon finish, these
+ * pausing tasks will become invisible.
+ * These need to be ordered since the order must be restored if there is no task-switch.
+ */
+ private ArrayList<TaskState> mPausingTasks = null;
+
+ /**
+ * List of tasks that we are switching to. Upon finish, these will remain visible and
+ * on top.
+ */
+ private ArrayList<TaskState> mOpeningTasks = null;
+
+ private WindowContainerToken mPipTask = null;
+ private WindowContainerToken mRecentsTask = null;
+ private int mRecentsTaskId = -1;
+ private TransitionInfo mInfo = null;
+ private boolean mOpeningSeparateHome = false;
+ private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+ private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ private IBinder mTransition = null;
+ private boolean mKeyguardLocked = false;
+ private boolean mWillFinishToHome = false;
+
+ /** The animation is idle, waiting for the user to choose a task to switch to. */
+ private static final int STATE_NORMAL = 0;
+
+ /** The user chose a new task to switch to and the animation is animating to it. */
+ private static final int STATE_NEW_TASK = 1;
+
+ /** The latest state that the recents animation is operating in. */
+ private int mState = STATE_NORMAL;
+
+ RecentsController(IRecentsAnimationRunner listener) {
+ mListener = listener;
+ mDeathHandler = () -> mExecutor.execute(() -> {
+ if (mListener == null) return;
+ if (mFinishCB != null) {
+ finish(mWillFinishToHome, false /* leaveHint */);
+ }
+ });
+ try {
+ mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
+ } catch (RemoteException e) {
+ mListener = null;
+ }
+ }
+
+ void setTransition(IBinder transition) {
+ mTransition = transition;
+ }
+
+ void cancel() {
+ // restoring (to-home = false) involves submitting more WM changes, so by default, use
+ // toHome = true when canceling.
+ cancel(true /* toHome */);
+ }
+
+ void cancel(boolean toHome) {
+ if (mFinishCB != null && mListener != null) {
+ try {
+ mListener.onAnimationCanceled(null, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ }
+ finish(toHome, false /* userLeave */);
+ } else {
+ cleanUp();
+ }
+ }
+
+ /**
+ * Sends a cancel message to the recents animation with snapshots. Used to trigger a
+ * "replace-with-screenshot" like behavior.
+ */
+ private boolean sendCancelWithSnapshots() {
+ int[] taskIds = null;
+ TaskSnapshot[] snapshots = null;
+ if (mPausingTasks.size() > 0) {
+ taskIds = new int[mPausingTasks.size()];
+ snapshots = new TaskSnapshot[mPausingTasks.size()];
+ try {
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
+ mPausingTasks.get(0).mTaskInfo.taskId);
+ }
+ } catch (RemoteException e) {
+ taskIds = null;
+ snapshots = null;
+ }
+ }
+ try {
+ mListener.onAnimationCanceled(taskIds, snapshots);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling recents animation", e);
+ return false;
+ }
+ return true;
+ }
+
+ void cleanUp() {
+ if (mListener != null && mDeathHandler != null) {
+ mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
+ mDeathHandler = null;
+ }
+ mListener = null;
+ mFinishCB = null;
+ // clean-up leash surfacecontrols and anything that might reference them.
+ if (mLeashMap != null) {
+ for (int i = 0; i < mLeashMap.size(); ++i) {
+ mLeashMap.valueAt(i).release();
+ }
+ mLeashMap = null;
+ }
+ mFinishTransaction = null;
+ mPausingTasks = null;
+ mOpeningTasks = null;
+ mInfo = null;
+ mTransition = null;
+ mControllers.remove(this);
+ }
+
+ boolean start(TransitionInfo info, SurfaceControl.Transaction t,
+ SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
+ if (mListener == null || mTransition == null) {
+ cleanUp();
+ return false;
+ }
+ // First see if this is a valid recents transition.
+ boolean hasPausingTasks = false;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isWallpaper(change)) continue;
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ hasPausingTasks = true;
+ continue;
+ }
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ mRecentsTask = taskInfo.token;
+ mRecentsTaskId = taskInfo.taskId;
+ }
+ }
+ if (mRecentsTask == null || !hasPausingTasks) {
+ // Recents is already running apparently, so this is a no-op.
+ Slog.e(TAG, "Tried to start recents while it is already running. recents="
+ + mRecentsTask);
+ cleanUp();
+ return false;
+ }
+
+ mInfo = info;
+ mFinishCB = finishCB;
+ mFinishTransaction = finishT;
+ mPausingTasks = new ArrayList<>();
+ mOpeningTasks = new ArrayList<>();
+ mLeashMap = new ArrayMap<>();
+ mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
+ TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
+ // About layering: we divide up the "layer space" into 3 regions (each the size of
+ // the change count). This lets us categorize things into above/below/between
+ // while maintaining their relative ordering.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (TransitionUtil.isWallpaper(change)) {
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ // wallpapers go into the "below" layer space
+ info.getChanges().size() - i, info, t, mLeashMap);
+ wallpapers.add(target);
+ // Make all the wallpapers opaque since we want them visible from the start
+ t.setAlpha(target.leash, 1);
+ } else if (leafTaskFilter.test(change)) {
+ // start by putting everything into the "below" layer space.
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ info.getChanges().size() - i, info, t, mLeashMap);
+ apps.add(target);
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ // raise closing (pausing) task to "above" layer so it isn't covered
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ mPausingTasks.add(new TaskState(change, target.leash));
+ if (taskInfo.pictureInPictureParams != null
+ && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+ mPipTask = taskInfo.token;
+ }
+ } else if (taskInfo != null
+ && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ // There's a 3p launcher, so make sure recents goes above that.
+ t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // do nothing
+ } else if (TransitionUtil.isOpeningType(change.getMode())) {
+ mOpeningTasks.add(new TaskState(change, target.leash));
+ }
+ }
+ }
+ t.apply();
+ try {
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
+ new Rect(0, 0, 0, 0), new Rect());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel();
+ }
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ void merge(TransitionInfo info, SurfaceControl.Transaction t,
+ Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishCB == null) {
+ // This was no-op'd (likely a repeated start) and we've already sent finish.
+ return;
+ }
+ if (info.getType() == TRANSIT_SLEEP) {
+ // A sleep event means we need to stop animations immediately, so cancel here.
+ cancel();
+ return;
+ }
+ ArrayList<TransitionInfo.Change> openingTasks = null;
+ ArrayList<TransitionInfo.Change> closingTasks = null;
+ mOpeningSeparateHome = false;
+ TransitionInfo.Change recentsOpening = null;
+ boolean foundRecentsClosing = false;
+ boolean hasChangingApp = false;
+ final TransitionUtil.LeafTaskFilter leafTaskFilter =
+ new TransitionUtil.LeafTaskFilter();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final boolean isLeafTask = leafTaskFilter.test(change);
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ if (mRecentsTask.equals(change.getContainer())) {
+ recentsOpening = change;
+ } else if (isLeafTask) {
+ if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // This is usually a 3p launcher
+ mOpeningSeparateHome = true;
+ }
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ }
+ openingTasks.add(change);
+ }
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ if (mRecentsTask.equals(change.getContainer())) {
+ foundRecentsClosing = true;
+ } else if (isLeafTask) {
+ if (closingTasks == null) {
+ closingTasks = new ArrayList<>();
+ }
+ closingTasks.add(change);
+ }
+ } else if (change.getMode() == TRANSIT_CHANGE) {
+ // Finish recents animation if the display is changed, so the default
+ // transition handler can play the animation such as rotation effect.
+ if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+ cancel(mWillFinishToHome);
+ return;
+ }
+ hasChangingApp = true;
+ }
+ }
+ if (hasChangingApp && foundRecentsClosing) {
+ // This happens when a visible app is expanding (usually PiP). In this case,
+ // that transition probably has a special-purpose animation, so finish recents
+ // now and let it do its animation (since recents is going to be occluded).
+ sendCancelWithSnapshots();
+ mExecutor.executeDelayed(
+ () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+ return;
+ }
+ if (recentsOpening != null) {
+ // the recents task re-appeared. This happens if the user gestures before the
+ // task-switch (NEW_TASK) animation finishes.
+ if (mState == STATE_NORMAL) {
+ Slog.e(TAG, "Returning to recents while recents is already idle.");
+ }
+ if (closingTasks == null || closingTasks.size() == 0) {
+ Slog.e(TAG, "Returning to recents without closing any opening tasks.");
+ }
+ // Setup may hide it initially since it doesn't know that overview was still active.
+ t.show(recentsOpening.getLeash());
+ t.setAlpha(recentsOpening.getLeash(), 1.f);
+ mState = STATE_NORMAL;
+ }
+ boolean didMergeThings = false;
+ if (closingTasks != null) {
+ // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+ for (int i = 0; i < closingTasks.size(); ++i) {
+ final TransitionInfo.Change change = closingTasks.get(i);
+ int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+ if (openingIdx < 0) {
+ Slog.e(TAG, "Back to existing recents animation from an unrecognized "
+ + "task: " + change.getTaskInfo().taskId);
+ continue;
+ }
+ mPausingTasks.add(mOpeningTasks.remove(openingIdx));
+ didMergeThings = true;
+ }
+ }
+ RemoteAnimationTarget[] appearedTargets = null;
+ if (openingTasks != null && openingTasks.size() > 0) {
+ // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+ // enter NEW_TASK state since this will start the switch-to animation.
+ final int layer = mInfo.getChanges().size() * 3;
+ appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+ for (int i = 0; i < openingTasks.size(); ++i) {
+ final TransitionInfo.Change change = openingTasks.get(i);
+ int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ if (pausingIdx >= 0) {
+ // Something is showing/opening a previously-pausing app.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
+ // Setup hides opening tasks initially, so make it visible again (since we
+ // are already showing it).
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else {
+ // We are receiving new opening tasks, so convert to onTasksAppeared.
+ appearedTargets[i] = TransitionUtil.newTarget(
+ change, layer, info, t, mLeashMap);
+ // reparent into the original `mInfo` since that's where we are animating.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
+ t.setLayer(appearedTargets[i].leash, layer);
+ mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+ }
+ }
+ didMergeThings = true;
+ mState = STATE_NEW_TASK;
+ }
+ if (!didMergeThings) {
+ // Didn't recognize anything in incoming transition so don't merge it.
+ Slog.w(TAG, "Don't know how to merge this transition.");
+ return;
+ }
+ // At this point, we are accepting the merge.
+ t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ if (appearedTargets == null) return;
+ try {
+ mListener.onTasksAppeared(appearedTargets);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
+ }
+ }
+
+ @Override
+ public TaskSnapshot screenshotTask(int taskId) {
+ try {
+ return ActivityTaskManager.getService().takeTaskSnapshot(taskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to screenshot task", e);
+ }
+ return null;
+ }
+
+ @Override
+ public void setInputConsumerEnabled(boolean enabled) {
+ mExecutor.execute(() -> {
+ if (mFinishCB == null || !enabled) return;
+ // transient launches don't receive focus automatically. Since we are taking over
+ // the gesture now, take focus explicitly.
+ // This also moves recents back to top if the user gestured before a switch
+ // animation finished.
+ try {
+ ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set focused task", e);
+ }
+ });
+ }
+
+ @Override
+ public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+ }
+
+ @Override
+ public void setFinishTaskTransaction(int taskId,
+ PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
+ mExecutor.execute(() -> {
+ if (mFinishCB == null) return;
+ mPipTransaction = finishTransaction;
+ });
+ }
+
+ @Override
+ @SuppressLint("NewApi")
+ public void finish(boolean toHome, boolean sendUserLeaveHint) {
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+ }
+
+ private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+ if (mFinishCB == null) {
+ Slog.e(TAG, "Duplicate call to finish");
+ return;
+ }
+ final Transitions.TransitionFinishCallback finishCB = mFinishCB;
+ mFinishCB = null;
+
+ final SurfaceControl.Transaction t = mFinishTransaction;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (mKeyguardLocked && mRecentsTask != null) {
+ if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
+ else wct.restoreTransientOrder(mRecentsTask);
+ }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ // The gesture is returning to the pausing-task(s) rather than continuing with
+ // recents, so end the transition by moving the app back to the top (and also
+ // re-showing it's task).
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ // reverse order so that index 0 ends up on top
+ wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
+ t.show(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
+ // Special situation where 3p launcher was changed during recents (this happens
+ // during tapltests...). Here we get both "return to home" AND "home opening".
+ // This is basically going home, but we have to restore the recents and home order.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ final TaskState state = mOpeningTasks.get(i);
+ if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // Make sure it is on top.
+ wct.reorder(state.mToken, true /* onTop */);
+ }
+ t.show(state.mTaskSurface);
+ }
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else {
+ // The general case: committing to recents, going home, or switching tasks.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ t.show(mOpeningTasks.get(i).mTaskSurface);
+ }
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ if (!sendUserLeaveHint) {
+ // This means recents is not *actually* finishing, so of course we gotta
+ // do special stuff in WMCore to accommodate.
+ wct.setDoNotPip(mPausingTasks.get(i).mToken);
+ }
+ // Since we will reparent out of the leashes, pre-emptively hide the child
+ // surface to match the leash. Otherwise, there will be a flicker before the
+ // visibility gets committed in Core when using split-screen (in splitscreen,
+ // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+ t.hide(mPausingTasks.get(i).mTaskSurface);
+ }
+ if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
+ t.show(mInfo.getChange(mPipTask).getLeash());
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+ mInfo.getChange(mPipTask).getLeash(), t);
+ mPipTask = null;
+ mPipTransaction = null;
+ }
+ }
+ cleanUp();
+ finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+ }
+
+ @Override
+ public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+ }
+
+ @Override
+ public void cleanupScreenshot() {
+ }
+
+ @Override
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ mExecutor.execute(() -> {
+ mWillFinishToHome = willFinishToHome;
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#removeTask
+ */
+ @Override
+ public boolean removeTask(int taskId) {
+ return false;
+ }
+
+ /**
+ * @see IRecentsAnimationController#detachNavigationBarFromApp
+ */
+ @Override
+ public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+ mExecutor.execute(() -> {
+ if (mTransition == null) return;
+ try {
+ ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to detach the navigation bar from app", e);
+ }
+ });
+ }
+
+ /**
+ * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+ */
+ @Override
+ public void animateNavigationBarToApp(long duration) {
+ }
+ };
+
+ /** Utility class to track the state of a task as-seen by recents. */
+ private static class TaskState {
+ WindowContainerToken mToken;
+ ActivityManager.RunningTaskInfo mTaskInfo;
+
+ /** The surface/leash of the task provided by Core. */
+ SurfaceControl mTaskSurface;
+
+ /** The (local) animation-leash created for this task. */
+ SurfaceControl mLeash;
+
+ TaskState(TransitionInfo.Change change, SurfaceControl leash) {
+ mToken = change.getContainer();
+ mTaskInfo = change.getTaskInfo();
+ mTaskSurface = change.getLeash();
+ mLeash = leash;
+ }
+
+ static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken.equals(change.getContainer())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String toString() {
+ return "" + mToken + " : " + mLeash;
+ }
+ }
+
+ /**
+ * An interface for a mixed handler to receive information about recents requests (since these
+ * come into this handler directly vs from WMCore request).
+ */
+ public interface RecentsMixedHandler {
+ /**
+ * Called when a recents request comes in. The handler can add operations to outWCT. If
+ * the handler wants to "accept" the transition, it should return itself; otherwise, it
+ * should return `null`.
+ *
+ * If a mixed-handler accepts this recents, it will be the de-facto handler for this
+ * transition and is required to call the associated {@link #startAnimation},
+ * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
+ */
+ Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT);
+
+ /**
+ * Reports the transition token associated with the accepted recents request. If there was
+ * a problem starting the request, this will be called with `null`.
+ */
+ void setRecentsTransition(@Nullable IBinder transition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1cf3a896b68e..e09c3c9e4d3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -22,6 +22,8 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -35,6 +37,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -51,6 +54,7 @@ import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -125,6 +129,7 @@ class SplitScreenTransitions {
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -132,7 +137,7 @@ class SplitScreenTransitions {
t.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
t.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -142,8 +147,9 @@ class SplitScreenTransitions {
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ final Point rootOffset = info.getRoot(rootIdx).getOffset();
+ startBounds.offset(-rootOffset.x, -rootOffset.y);
+ endBounds.offset(-rootOffset.x, -rootOffset.y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
boolean isRootOrSplitSideRoot = change.getParent() == null
@@ -179,6 +185,33 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
}
+ void applyDismissTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken topRoot,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ if (mPendingDismiss.mDismissTop != STAGE_TYPE_UNDEFINED) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ mFinishTransaction = finishTransaction;
+
+ startTransaction.apply();
+
+ final SplitDecorManager topDecor = mPendingDismiss.mDismissTop == STAGE_TYPE_MAIN
+ ? mainDecor : sideDecor;
+ topDecor.fadeOutDecor(() -> {
+ mTransitions.getMainExecutor().execute(() -> {
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ });
+ } else {
+ playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mainRoot, sideRoot, topRoot);
+ }
+ }
+
void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -200,8 +233,11 @@ class SplitScreenTransitions {
SplitDecorManager decor = mainRoot.equals(change.getContainer())
? mainDecor : sideDecor;
+
+ // This is to ensure onFinished be called after all animations ended.
ValueAnimator va = new ValueAnimator();
mAnimations.add(va);
+
decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
decor.onResized(startTransaction, () -> {
mTransitions.getMainExecutor().execute(() -> {
@@ -307,6 +343,12 @@ class SplitScreenTransitions {
IBinder startResizeTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler,
@Nullable TransitionFinishedCallback finishCallback) {
+ if (mPendingResize != null) {
+ mPendingResize.cancel(null);
+ mAnimations.clear();
+ onFinish(null /* wct */, null /* wctCB */);
+ }
+
IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
setResizeTransition(transition, finishCallback);
return transition;
@@ -499,7 +541,7 @@ class SplitScreenTransitions {
}
private boolean isOpeningTransition(TransitionInfo info) {
- return Transitions.isOpeningType(info.getType())
+ return TransitionUtil.isOpeningType(info.getType())
|| info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
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 71ee690146f9..a1eaf851da23 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
@@ -20,7 +20,6 @@ import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
-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;
@@ -61,8 +60,8 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.transition.Transitions.isClosingType;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -135,6 +134,7 @@ import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -259,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- private final SplitScreenTransitions.TransitionFinishedCallback
- mRecentTransitionFinishedCallback =
- new SplitScreenTransitions.TransitionFinishedCallback() {
- @Override
- public void onFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current
- // split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT,
- false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
-
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
-
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
@@ -388,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mMainStage.isActive();
}
+ /** Checks if `transition` is a pending enter-split transition. */
+ public boolean isPendingEnter(IBinder transition) {
+ return mSplitTransitions.isPendingEnter(transition);
+ }
+
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -651,6 +625,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
addActivityOptions(options1, mSideStage);
@@ -665,6 +640,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
addActivityOptions(options1, mSideStage);
@@ -678,6 +654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
addActivityOptions(options1, mSideStage);
@@ -696,10 +673,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
- if (mMainStage.isActive()) {
- mMainStage.evictAllChildren(wct);
- mSideStage.evictAllChildren(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(wct, false /* reparent */);
@@ -1107,6 +1081,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictInvisibleChildren(wct);
}
+ void prepareEvictChildTasksIfSplitActive(WindowContainerTransaction wct) {
+ if (mMainStage.isActive()) {
+ mMainStage.evictAllChildren(wct);
+ mSideStage.evictAllChildren(wct);
+ }
+ }
+
Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
@Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
@@ -1455,8 +1436,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
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);
+ // Set the dismiss-to-top side to fullscreen for dismiss transition.
+ // Reparent the non-dismiss-to-top side to properly update its visibility.
+ if (stageToTop == STAGE_TYPE_MAIN) {
+ wct.setBounds(mMainStage.mRootTaskInfo.token, null /* bounds */);
+ mSideStage.removeAllTasks(wct, false /* toTop */);
+ } else if (stageToTop == STAGE_TYPE_SIDE) {
+ wct.setBounds(mSideStage.mRootTaskInfo.token, null /* bounds */);
+ mMainStage.deactivate(wct, false /* toTop */);
+ } else {
+ mSideStage.removeAllTasks(wct, false /* toTop */);
+ mMainStage.deactivate(wct, false /* toTop */);
+ }
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -2011,10 +2002,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
- updateWindowBounds(layout, wct);
+ boolean sizeChanged = updateWindowBounds(layout, wct);
+ if (!sizeChanged) return;
+
sendOnBoundsChanged();
if (ENABLE_SHELL_TRANSITIONS) {
- mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+ mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
+ mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) ->
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"));
} else {
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -2033,13 +2028,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* 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
+ *
+ * @return true if stage bounds actually .
*/
- private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
}
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
@@ -2240,19 +2238,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
} 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.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionFinishedCallback);
- } else if (mSplitTransitions.mPendingRecent == null) {
- // If split-task is not controlled by recents animation
- // and occluded by the other fullscreen task, dismiss both.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
- mSplitTransitions.setDismissTransition(
- transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ if (request.getRemoteTransition() != null) {
+ // starting recents/home, so don't handle this and let it fall-through to
+ // the remote handler.
+ return null;
+ }
+ // Need to use the old stuff for non-remote animations, otherwise we don't
+ // exit split-screen.
+ mSplitTransitions.setRecentTransition(transition, null /* remote */,
+ this::onRecentsInSplitAnimationFinish);
}
}
} else {
@@ -2358,7 +2353,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Use normal animations.
return false;
- } else if (mMixedHandler != null && hasDisplayChange(info)) {
+ } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
// A display-change has been un-expectedly inserted into the transition. Redirect
// handling to the mixed-handler to deal with splitting it up.
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
@@ -2382,10 +2377,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
shouldAnimate = startPendingEnterAnimation(
transition, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingRecent(transition)) {
- shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
+ onRecentsInSplitAnimationStart(startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+ if (shouldAnimate) {
+ mSplitTransitions.applyDismissTransition(transition, info,
+ startTransaction, finishTransaction, finishCallback, mRootTaskInfo.token,
+ mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
+ return true;
+ }
} else if (mSplitTransitions.isPendingResize(transition)) {
mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
@@ -2401,15 +2403,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- private boolean hasDisplayChange(TransitionInfo info) {
- boolean has = false;
- for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0;
- }
- return has;
- }
-
/** Called to clean-up state and do house-keeping after the animation is done. */
public void onTransitionAnimationComplete() {
// If still playing, let it finish.
@@ -2539,37 +2532,44 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// aren't serialized with transition callbacks.
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
- if (mMainStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mMainStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ if (toStage == STAGE_TYPE_UNDEFINED) {
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
}
- Log.w(TAG, "Expected onTaskVanished on " + mMainStage
- + " to have been called with [" + tasksLeft.toString()
- + "] before startAnimation().");
- }
- if (mSideStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mSideStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
}
- Log.w(TAG, "Expected onTaskVanished on " + mSideStage
- + " to have been called with [" + tasksLeft.toString()
- + "] 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(dismissReason) && 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);
+ if (toStage == STAGE_TYPE_UNDEFINED) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ recentTasks.removeSplitPair(taskInfo.taskId);
+ }
}
+ } else {
+ recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
+ recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
}
}
});
@@ -2582,6 +2582,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Reset crops so they don't interfere with subsequent launches
t.setCrop(mMainStage.mRootLeash, null);
t.setCrop(mSideStage.mRootLeash, null);
+ // Hide the non-top stage and set the top one to the fullscreen position.
+ if (toStage != STAGE_TYPE_UNDEFINED) {
+ t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
+ t.setPosition(toStage == STAGE_TYPE_MAIN
+ ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+ }
if (toStage == STAGE_TYPE_UNDEFINED) {
logExit(dismissReason);
@@ -2608,16 +2614,53 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
+ } else {
+ final @SplitScreen.StageType int dismissTop = dismissTransition.mDismissTop;
+ // Reparent all tasks after dismiss transition finished.
+ dismissTransition.setFinishedCallback(
+ new SplitScreenTransitions.TransitionFinishedCallback() {
+ @Override
+ public void onFinished(WindowContainerTransaction wct,
+ SurfaceControl.Transaction t) {
+ mSideStage.removeAllTasks(wct, dismissTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, dismissTop == STAGE_TYPE_MAIN);
+ }
+ });
}
addDividerBarToTransition(info, finishT, false /* show */);
return true;
}
- private boolean startPendingRecentAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ /** Call this when starting the open-recents animation while split-screen is active. */
+ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
setDividerVisibility(false, t);
- return true;
+ }
+
+ /** Call this when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
new file mode 100644
index 000000000000..28be0efc38f6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module TV splitscreen owner
+galinap@google.com
+bronger@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
new file mode 100644
index 000000000000..1d8a8d506c5c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
@@ -0,0 +1,218 @@
+/*
+ * 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.splitscreen.tv;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+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_DOCK_DIVIDER;
+import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.view.LayoutInflater;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+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.common.split.SplitScreenConstants;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * Handles the interaction logic with the {@link TvSplitMenuView}.
+ * A bridge between {@link TvStageCoordinator} and {@link TvSplitMenuView}.
+ */
+public class TvSplitMenuController implements TvSplitMenuView.Listener {
+
+ private static final String TAG = TvSplitMenuController.class.getSimpleName();
+ private static final String ACTION_SHOW_MENU = "com.android.wm.shell.splitscreen.SHOW_MENU";
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+ private final Context mContext;
+ private final StageController mStageController;
+ private final SystemWindows mSystemWindows;
+ private final Handler mMainHandler;
+
+ private final TvSplitMenuView mSplitMenuView;
+
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
+ private final int mTvButtonFadeAnimationDuration;
+
+ public TvSplitMenuController(Context context, StageController stageController,
+ SystemWindows systemWindows, Handler mainHandler) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mStageController = stageController;
+ mSystemWindows = systemWindows;
+
+ mTvButtonFadeAnimationDuration = context.getResources()
+ .getInteger(R.integer.tv_window_menu_fade_animation_duration);
+
+ mSplitMenuView = (TvSplitMenuView) LayoutInflater.from(context)
+ .inflate(R.layout.tv_split_menu_view, null);
+ mSplitMenuView.setListener(this);
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
+ }
+
+ /**
+ * Adds the menu view for the splitscreen to SystemWindows.
+ */
+ void addSplitMenuViewToSystemWindows() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ mContext.getResources().getDisplayMetrics().widthPixels,
+ mContext.getResources().getDisplayMetrics().heightPixels,
+ TYPE_DOCK_DIVIDER,
+ FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ mSplitMenuView.setAlpha(0);
+ mSystemWindows.addView(mSplitMenuView, lp, DEFAULT_DISPLAY, SHELL_ROOT_LAYER_DIVIDER);
+ }
+
+ /**
+ * Removes the menu view for the splitscreen from SystemWindows.
+ */
+ void removeSplitMenuViewFromSystemWindows() {
+ mSystemWindows.removeView(mSplitMenuView);
+ }
+
+ /**
+ * Registers BroadcastReceiver when split screen mode is entered.
+ */
+ void registerBroadcastReceiver() {
+ mActionBroadcastReceiver.register();
+ }
+
+ /**
+ * Unregisters BroadcastReceiver when split screen mode is entered.
+ */
+ void unregisterBroadcastReceiver() {
+ mActionBroadcastReceiver.unregister();
+ }
+
+ @Override
+ public void onBackPress() {
+ setMenuVisibility(false);
+ }
+
+ @Override
+ public void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus) {
+ setMenuVisibility(false);
+ mStageController.grantFocusToStage(stageToFocus);
+ }
+
+ @Override
+ public void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose) {
+ setMenuVisibility(false);
+ mStageController.exitStage(stageToClose);
+ }
+
+ @Override
+ public void onSwapPress() {
+ mStageController.swapStages();
+ }
+
+ private void setMenuVisibility(boolean visible) {
+ applyMenuVisibility(visible);
+ setMenuFocus(visible);
+ }
+
+ private void applyMenuVisibility(boolean visible) {
+ float alphaTarget = visible ? 1F : 0F;
+
+ if (mSplitMenuView.getAlpha() == alphaTarget) {
+ return;
+ }
+
+ mSplitMenuView.animate()
+ .alpha(alphaTarget)
+ .setDuration(mTvButtonFadeAnimationDuration)
+ .withStartAction(() -> {
+ if (alphaTarget != 0) {
+ mSplitMenuView.setVisibility(VISIBLE);
+ }
+ })
+ .withEndAction(() -> {
+ if (alphaTarget == 0) {
+ mSplitMenuView.setVisibility(INVISIBLE);
+ }
+ });
+
+ }
+
+ private void setMenuFocus(boolean focused) {
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null,
+ mSystemWindows.getFocusGrantToken(mSplitMenuView), focused);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Unable to update focus, %s", TAG, e);
+ }
+ }
+
+ interface StageController {
+ void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus);
+ void exitStage(@SplitScreenConstants.SplitPosition int stageToClose);
+ void swapStages();
+ }
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+
+ final IntentFilter mIntentFilter;
+ {
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_SHOW_MENU);
+ }
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+ mMainHandler);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (ACTION_SHOW_MENU.equals(action)) {
+ setMenuVisibility(true);
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
new file mode 100644
index 000000000000..88e9757a9b31
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen.tv;
+
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.KEYCODE_BACK;
+
+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 android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.split.SplitScreenConstants;
+
+/**
+ * A View for the Menu Window.
+ */
+public class TvSplitMenuView extends LinearLayout implements View.OnClickListener {
+
+ private Listener mListener;
+
+ public TvSplitMenuView(Context context) {
+ super(context);
+ }
+
+ public TvSplitMenuView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TvSplitMenuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ initButtons();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener == null) return;
+
+ final int id = v.getId();
+ if (id == R.id.tv_split_main_menu_focus_button) {
+ mListener.onFocusStage(SPLIT_POSITION_TOP_OR_LEFT);
+ } else if (id == R.id.tv_split_main_menu_close_button) {
+ mListener.onCloseStage(SPLIT_POSITION_TOP_OR_LEFT);
+ } else if (id == R.id.tv_split_side_menu_focus_button) {
+ mListener.onFocusStage(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ } else if (id == R.id.tv_split_side_menu_close_button) {
+ mListener.onCloseStage(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ } else if (id == R.id.tv_split_menu_swap_stages) {
+ mListener.onSwapPress();
+ }
+ }
+
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == ACTION_DOWN) {
+ if (event.getKeyCode() == KEYCODE_BACK) {
+ if (mListener != null) {
+ mListener.onBackPress();
+ return true;
+ }
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ private void initButtons() {
+ findViewById(R.id.tv_split_main_menu_focus_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_main_menu_close_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_side_menu_focus_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_side_menu_close_button).setOnClickListener(this);
+ findViewById(R.id.tv_split_menu_swap_stages).setOnClickListener(this);
+ }
+
+ void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ interface Listener {
+ /** "Back" button from the remote control */
+ void onBackPress();
+
+ /** Menu Action Buttons */
+
+ void onFocusStage(@SplitScreenConstants.SplitPosition int stageToFocus);
+
+ void onCloseStage(@SplitScreenConstants.SplitPosition int stageToClose);
+
+ void onSwapPress();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
new file mode 100644
index 000000000000..46d2a5a11671
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen.tv;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.os.Handler;
+
+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.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * Class inherits from {@link SplitScreenController} and provides {@link TvStageCoordinator}
+ * for Split Screen on TV.
+ */
+public class TvSplitScreenController extends SplitScreenController {
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final ShellExecutor mMainExecutor;
+ private final DisplayController mDisplayController;
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final IconProvider mIconProvider;
+ private final Optional<RecentTasksController> mRecentTasksOptional;
+
+ private final Handler mMainHandler;
+ private final SystemWindows mSystemWindows;
+
+ public TvSplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor,
+ Handler mainHandler,
+ SystemWindows systemWindows) {
+ super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
+ syncQueue, rootTDAOrganizer, displayController, displayImeController,
+ displayInsetsController, dragAndDropController, transitions, transactionPool,
+ iconProvider, recentTasks, mainExecutor);
+
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = recentTasks;
+
+ mMainHandler = mainHandler;
+ mSystemWindows = systemWindows;
+ }
+
+ /**
+ * Provides Tv-specific StageCoordinator.
+ * @return {@link TvStageCoordinator}
+ */
+ @Override
+ protected StageCoordinator createStageCoordinator() {
+ return new TvStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mTaskOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool,
+ mIconProvider, mMainExecutor, mMainHandler,
+ mRecentTasksOptional, mSystemWindows);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
new file mode 100644
index 000000000000..4d563fbb7f04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.splitscreen.tv;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitScreenConstants;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * Expands {@link StageCoordinator} functionality with Tv-specific methods.
+ */
+public class TvStageCoordinator extends StageCoordinator
+ implements TvSplitMenuController.StageController {
+
+ private final TvSplitMenuController mTvSplitMenuController;
+
+ public TvStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ ShellTaskOrganizer taskOrganizer, DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider, ShellExecutor mainExecutor,
+ Handler mainHandler,
+ Optional<RecentTasksController> recentTasks,
+ SystemWindows systemWindows) {
+ super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
+ displayInsetsController, transitions, transactionPool, iconProvider,
+ mainExecutor, recentTasks);
+
+ mTvSplitMenuController = new TvSplitMenuController(context, this,
+ systemWindows, mainHandler);
+
+ }
+
+ @Override
+ protected void onSplitScreenEnter() {
+ mTvSplitMenuController.addSplitMenuViewToSystemWindows();
+ mTvSplitMenuController.registerBroadcastReceiver();
+ }
+
+ @Override
+ protected void onSplitScreenExit() {
+ mTvSplitMenuController.unregisterBroadcastReceiver();
+ mTvSplitMenuController.removeSplitMenuViewFromSystemWindows();
+ }
+
+ @Override
+ public void grantFocusToStage(@SplitScreenConstants.SplitPosition int stageToFocus) {
+ super.grantFocusToStage(stageToFocus);
+ }
+
+ @Override
+ public void exitStage(@SplitScreenConstants.SplitPosition int stageToClose) {
+ super.exitStage(stageToClose);
+ }
+
+ /**
+ * Swaps the stages inside the SplitLayout.
+ */
+ @Override
+ public void swapStages() {
+ onDoubleTappedDivider();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
new file mode 100644
index 000000000000..1ddd8f9a3a14
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+// abstract class to create splash screen window(or windowless window)
+abstract class AbsSplashWindowCreator {
+ protected static final String TAG = StartingWindowController.TAG;
+ protected final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ protected final Context mContext;
+ protected final DisplayManager mDisplayManager;
+ protected final ShellExecutor mSplashScreenExecutor;
+ protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager;
+
+ private StartingSurface.SysuiProxy mSysuiProxy;
+
+ AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mSplashscreenContentDrawer = contentDrawer;
+ mContext = context;
+ mSplashScreenExecutor = splashScreenExecutor;
+ mDisplayManager = displayManager;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ return splashScreenThemeResId != 0
+ ? splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ }
+
+ protected Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
+ }
+
+ void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
+ mSysuiProxy = sysuiProxy;
+ }
+
+ protected void requestTopUi(boolean requestTopUi) {
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(requestTopUi, TAG);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
new file mode 100644
index 000000000000..20c4d5ae5f58
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+class SnapshotWindowCreator {
+ private final ShellExecutor mMainExecutor;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+
+ SnapshotWindowCreator(ShellExecutor mainExecutor,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ mMainExecutor = mainExecutor;
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ final int taskId = startingWindowInfo.taskInfo.taskId;
+ // Remove any existing starting window for this task before adding.
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
+ startingWindowInfo.appToken, snapshot, mMainExecutor,
+ () -> mStartingWindowRecordManager.removeWindow(taskId, true));
+ if (surface != null) {
+ final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
+ startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+ }
+
+ private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private final TaskSnapshotWindow mTaskSnapshotWindow;
+
+ SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
+ int activityType, ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mTaskSnapshotWindow = taskSnapshotWindow;
+ mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ mTaskSnapshotWindow.removeImmediately();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mTaskSnapshotWindow.hasImeSurface();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 839d56a43222..dc91a11dc64f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,19 +16,19 @@
package com.android.wm.shell.startingsurface;
+import static android.content.Context.CONTEXT_RESTRICTED;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_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;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -48,9 +48,11 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -58,7 +60,9 @@ import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
@@ -89,6 +93,25 @@ import java.util.function.UnaryOperator;
public class SplashscreenContentDrawer {
private static final String TAG = StartingWindowController.TAG;
+ /**
+ * The minimum duration during which the splash screen is shown when the splash screen icon is
+ * animated.
+ */
+ static final long MINIMAL_ANIMATION_DURATION = 400L;
+
+ /**
+ * Allow the icon style splash screen to be displayed for longer to give time for the animation
+ * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
+ * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
+ */
+ static final long TIME_WINDOW_DURATION = 100L;
+
+ /**
+ * The maximum duration during which the splash screen will be shown if the application is ready
+ * to show before the icon animation finishes.
+ */
+ static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+
// The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
// icon which it's non-transparent foreground area is similar to it's background area, then
// do not enlarge the foreground drawable.
@@ -134,6 +157,144 @@ public class SplashscreenContentDrawer {
}
/**
+ * Help method to create a layout parameters for a window.
+ */
+ static Context createContext(Context initContext, StartingWindowInfo windowInfo,
+ int theme, @StartingWindowInfo.StartingWindowType int suggestType,
+ DisplayManager displayManager) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return null;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+
+ 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 = displayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return null;
+ }
+ Context context = displayId == DEFAULT_DISPLAY
+ ? initContext : initContext.createDisplayContext(display);
+ if (context == null) {
+ return null;
+ }
+ if (theme != context.getThemeResId()) {
+ try {
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
+ context.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
+ return null;
+ }
+ }
+
+ final Configuration taskConfig = taskInfo.getConfiguration();
+ if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
+ 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(
+ com.android.internal.R.styleable.Window);
+ final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ try {
+ if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+ // 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.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: apply overrideConfig %s",
+ taskConfig);
+ context = overrideContext;
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
+ + taskId, e);
+ return null;
+ }
+ typedArray.recycle();
+ }
+ return context;
+ }
+
+ /**
+ * Creates the window layout parameters for splashscreen window.
+ */
+ static WindowManager.LayoutParams createLayoutParameters(Context context,
+ StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType,
+ CharSequence title, int pixelFormat, IBinder appToken) {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+ params.setFitInsetsSides(0);
+ params.setFitInsetsTypes(0);
+ params.format = pixelFormat;
+ int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ }
+ if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ } else {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ final int displayId = taskInfo.displayId;
+ // Assumes it's safe to show starting windows of launched apps while
+ // the keyguard is being hidden. This is okay because starting windows never show
+ // secret information.
+ // TODO(b/113840485): Occluded may not only happen on default display
+ if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ }
+
+ // Force the window flags: this is a fake window, so it is not really
+ // 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;
+ params.flags = windowFlags;
+ params.token = appToken;
+ params.packageName = activityInfo.packageName;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
+ if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+
+ params.setTitle("Splash Screen " + title);
+ return params;
+ }
+ /**
* Create a SplashScreenView object.
*
* In order to speed up the splash screen view to show on first frame, preparing the
@@ -223,10 +384,10 @@ public class SplashscreenContentDrawer {
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester = new DrawableColorTester(
- themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
- if (themeBGTester.passFilterRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
+ themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
+ if (themeBGTester.passFilterRatio() != 1) {
+ // the window background is translucent, unable to draw
+ Slog.w(TAG, "Window background is translucent, fill background with black color");
return getSystemBGColor();
} else {
return themeBGTester.getDominateColor();
@@ -248,6 +409,26 @@ public class SplashscreenContentDrawer {
return null;
}
+ /**
+ * Creates a SplashScreenView without read animatable icon and branding image.
+ */
+ SplashScreenView makeSimpleSplashScreenContentView(Context context,
+ StartingWindowInfo info, int themeBGColor) {
+ updateDensity();
+ mTmpAttrs.reset();
+ final ActivityInfo ai = info.targetActivityInfo != null
+ ? info.targetActivityInfo
+ : info.taskInfo.topActivityInfo;
+
+ final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
+ final SplashScreenView view = builder
+ .setWindowBGColor(themeBGColor)
+ .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+ .build();
+ view.setNotCopyable();
+ return view;
+ }
+
private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
@StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
@@ -263,7 +444,8 @@ public class SplashscreenContentDrawer {
final int themeBGColor = legacyDrawable != null
? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
: getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
- return new StartingWindowViewBuilder(context, ai)
+
+ return new SplashViewBuilder(context, ai)
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
@@ -322,6 +504,14 @@ public class SplashscreenContentDrawer {
private Drawable mSplashScreenIcon = null;
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
+
+ void reset() {
+ mWindowBgResId = 0;
+ mWindowBgColor = Color.TRANSPARENT;
+ mSplashScreenIcon = null;
+ mBrandingImage = null;
+ mIconBgColor = Color.TRANSPARENT;
+ }
}
/**
@@ -351,7 +541,7 @@ public class SplashscreenContentDrawer {
return appReadyDuration;
}
- private class StartingWindowViewBuilder {
+ private class SplashViewBuilder {
private final Context mContext;
private final ActivityInfo mActivityInfo;
@@ -364,27 +554,28 @@ public class SplashscreenContentDrawer {
/** @see #setAllowHandleSolidColor(boolean) **/
private boolean mAllowHandleSolidColor;
- StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
+ SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
mActivityInfo = aInfo;
}
- StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ SplashViewBuilder setWindowBGColor(@ColorInt int background) {
mThemeColor = background;
return this;
}
- StartingWindowViewBuilder overlayDrawable(Drawable overlay) {
+ SplashViewBuilder overlayDrawable(Drawable overlay) {
mOverlayDrawable = overlay;
return this;
}
- StartingWindowViewBuilder chooseStyle(int suggestType) {
+ SplashViewBuilder chooseStyle(int suggestType) {
mSuggestType = suggestType;
return this;
}
- StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ // Set up the UI thread for the View.
+ SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
mUiThreadInitTask = uiThreadInitTask;
return this;
}
@@ -395,7 +586,7 @@ public class SplashscreenContentDrawer {
* android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
* callback, effectively copying the {@link SplashScreenView} into the client process.
*/
- StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
+ SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
mAllowHandleSolidColor = allowHandleSolidColor;
return this;
}
@@ -679,7 +870,7 @@ public class SplashscreenContentDrawer {
@Override
public float passFilterRatio() {
final int alpha = mColorDrawable.getAlpha();
- return (float) (alpha / 255);
+ return alpha / 255.0f;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 7f6bfd23f72b..e419462012e3 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
@@ -62,7 +62,7 @@ public class SplashscreenIconDrawableFactory {
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ boolean loadInDetail, Handler preDrawHandler) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
@@ -74,13 +74,13 @@ public class SplashscreenIconDrawableFactory {
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
foreground = new ImmobileIconDrawable(foregroundDrawable,
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
} else {
// Adaptive icon don't handle transparency so we draw the background of the adaptive
// icon with the same color as the window background color instead of using two layers
foreground = new ImmobileIconDrawable(
new AdaptiveForegroundDrawable(foregroundDrawable),
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
}
if (drawBackground) {
@@ -91,9 +91,9 @@ public class SplashscreenIconDrawableFactory {
}
static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
- int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ int iconSize, boolean loadInDetail, Handler preDrawHandler) {
return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
- loadInDetail, splashscreenWorkerHandler)};
+ loadInDetail, preDrawHandler)};
}
/**
@@ -107,14 +107,14 @@ public class SplashscreenIconDrawableFactory {
private Bitmap mIconBitmap;
ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
- Handler splashscreenWorkerHandler) {
+ Handler preDrawHandler) {
// This icon has lower density, don't scale it.
if (loadInDetail) {
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, iconSize));
} else {
final float scale = (float) iconSize / srcIconSize;
mMatrix.setScale(scale, scale);
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
new file mode 100644
index 000000000000..8a4d4c21194a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.internal.R;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.function.Supplier;
+
+/**
+ * A class which able to draw splash screen as the starting window for a task.
+ *
+ * In order to speed up, there will use two threads to creating a splash screen in parallel.
+ * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
+ * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
+ * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
+ * can synchronize on each frame.
+ *
+ * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
+ * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
+ * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
+ * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
+ * quickly.
+ *
+ * So basically we are using the spare time to prepare the SplashScreenView while splash screen
+ * thread is waiting for
+ * 1. WindowManager#addView(binder call to WM),
+ * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
+ * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
+ * always happen before #draw).
+ * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
+ * splash-screen background tread can make they execute in parallel, which ensure it is faster then
+ * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
+ *
+ * Here is the sequence to compare the difference between using single and two thread.
+ *
+ * Single thread:
+ * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
+ * -> draw -> AdaptiveIconDrawable#draw
+ *
+ * Two threads:
+ * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
+ * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
+ * directly).
+ */
+class SplashscreenWindowCreator extends AbsSplashWindowCreator {
+ private static final int LIGHT_BARS_MASK =
+ WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+ | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+ private final WindowManagerGlobal mWindowManagerGlobal;
+ private Choreographer mChoreographer;
+
+ /**
+ * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+ * rendered and that have not yet been removed by their client.
+ */
+ private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+ new SparseArray<>(1);
+
+ SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+ ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+ mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+ // replace with the default theme if the application didn't set
+ final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
+ if (context == null) {
+ return;
+ }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
+
+ // TODO(b/173975965) tracking performance
+ // Prepare the splash screen content view on splash screen worker thread in parallel, so the
+ // content view won't be blocked by binder call like addWindow and relayout.
+ // 1. Trigger splash screen worker thread to create SplashScreenView before/while
+ // Session#addWindow.
+ // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
+ // traversal, which will call Session#relayout on splash screen thread.
+ // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
+ // the same time the splash screen thread should be executing Session#relayout. Blocking the
+ // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
+
+ // Record whether create splash screen view success, notify to current thread after
+ // create splash screen view finished.
+ final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
+ rootLayout.setPadding(0, 0, 0, 0);
+ rootLayout.setFitsSystemWindows(false);
+ final Runnable setViewSynchronized = () -> {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
+ // waiting for setContentView before relayoutWindow
+ SplashScreenView contentView = viewSupplier.get();
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
+ ? (SplashWindowRecord) sRecord : null;
+ // If record == null, either the starting window added fail or removed already.
+ // Do not add this view if the token is mismatch.
+ if (record != null && windowInfo.appToken == record.mAppToken) {
+ // if view == null then creation of content view was failed.
+ if (contentView != null) {
+ try {
+ rootLayout.addView(contentView);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "failed set content view to starting window "
+ + "at taskId: " + taskId, e);
+ contentView = null;
+ }
+ }
+ record.setSplashScreenView(contentView);
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ };
+ requestTopUi(true);
+ mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
+ try {
+ if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
+ // We use the splash screen worker thread to create SplashScreenView while adding
+ // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
+ // And since Choreographer#doFrame won't happen immediately after adding the window,
+ // if the view is not added to the PhoneWindow on the first #doFrame, the view will
+ // not be rendered on the first frame. So here we need to synchronize the view on
+ // the window before first round relayoutWindow, which will happen after insets
+ // animation.
+ mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
+ final SplashWindowRecord record =
+ (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId);
+ if (record != null) {
+ record.parseAppSystemBarColor(context);
+ // Block until we get the background color.
+ final SplashScreenView contentView = viewSupplier.get();
+ if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ contentView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ final int lightBarAppearance =
+ ContrastColorUtil.isColorLight(
+ contentView.getInitBackgroundColor())
+ ? LIGHT_BARS_MASK : 0;
+ contentView.getWindowInsetsController()
+ .setSystemBarsAppearance(
+ lightBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+ }
+ } else {
+ // release the icon view host
+ final SplashScreenView contentView = viewSupplier.get();
+ if (contentView.getSurfaceHost() != null) {
+ SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
+ }
+ }
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
+ }
+ }
+
+ int estimateTaskBackgroundColor(TaskInfo taskInfo) {
+ if (taskInfo.topActivityInfo == null) {
+ return Color.TRANSPARENT;
+ }
+ final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+ final String packageName = activityInfo.packageName;
+ final int userId = taskInfo.userId;
+ final Context windowContext;
+ try {
+ windowContext = mContext.createPackageContextAsUser(
+ packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + taskInfo.userId, e);
+ return Color.TRANSPARENT;
+ }
+ try {
+ final IPackageManager packageManager = ActivityThread.getPackageManager();
+ final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
+ userId);
+ final int splashScreenThemeId = splashScreenThemeName != null
+ ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
+ : 0;
+
+ final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
+
+ if (theme != windowContext.getThemeResId()) {
+ windowContext.setTheme(theme);
+ }
+ return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.w(TAG, "failed get starting window background color at taskId: "
+ + taskInfo.taskId, e);
+ }
+ return Color.TRANSPARENT;
+ }
+
+ /**
+ * Called when the Task wants to copy the splash screen.
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord record =
+ mStartingWindowRecordManager.getRecord(taskId);
+ final SplashWindowRecord preView = record instanceof SplashWindowRecord
+ ? (SplashWindowRecord) record : null;
+ SplashScreenView.SplashScreenViewParcelable parcelable;
+ SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null;
+ if (splashScreenView != null && splashScreenView.isCopyable()) {
+ parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView);
+ parcelable.setClientCallback(
+ new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+ () -> onAppSplashScreenViewRemoved(taskId, false))));
+ splashScreenView.onCopied();
+ mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
+ } else {
+ parcelable = null;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Copying splash screen window view for task: %d with parcelable %b",
+ taskId, parcelable != null);
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
+
+ /**
+ * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
+ * or when the Activity is clean up.
+ *
+ * @param taskId The Task id on which the splash screen was attached
+ */
+ public void onAppSplashScreenViewRemoved(int taskId) {
+ onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ }
+
+ /**
+ * @param fromServer If true, this means the removal was notified by the server. This is only
+ * used for debugging purposes.
+ * @see #onAppSplashScreenViewRemoved(int)
+ */
+ private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+ SurfaceControlViewHost viewHost =
+ mAnimatedSplashScreenSurfaceHosts.get(taskId);
+ if (viewHost == null) {
+ return;
+ }
+ mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
+ fromServer ? "Server cleaned up" : "App removed", taskId);
+ SplashScreenView.releaseIconHost(viewHost);
+ }
+
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ boolean shouldSaveView = true;
+ final Context context = view.getContext();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
+ mWindowManagerGlobal.addView(view, params, display,
+ null /* parentWindow */, context.getUserId());
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ mWindowManagerGlobal.removeView(view, true /* immediate */);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ mStartingWindowRecordManager.removeWindow(taskId, true);
+ saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ return shouldSaveView;
+ }
+
+ private void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ final SplashWindowRecord tView =
+ new SplashWindowRecord(appToken, view, suggestType);
+ mStartingWindowRecordManager.addRecord(taskId, tView);
+ }
+
+ private void removeWindowInner(View decorView, boolean hideView) {
+ requestTopUi(false);
+ if (hideView) {
+ decorView.setVisibility(View.GONE);
+ }
+ mWindowManagerGlobal.removeView(decorView, false /* immediate */);
+ }
+
+ private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
+ private SplashScreenView mView;
+ private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
+ void setView(SplashScreenView view) {
+ synchronized (this) {
+ mView = view;
+ mIsViewSet = true;
+ notify();
+ }
+ }
+
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
+ @Override
+ @Nullable
+ public SplashScreenView get() {
+ synchronized (this) {
+ while (!mIsViewSet) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
+ return mView;
+ }
+ }
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private final IBinder mAppToken;
+ private final View mRootView;
+ @StartingWindowInfo.StartingWindowType private final int mSuggestType;
+ private final long mCreateTime;
+
+ private boolean mSetSplashScreen;
+ private SplashScreenView mSplashView;
+ private int mSystemBarAppearance;
+ private boolean mDrawsSystemBarBackgrounds;
+
+ SplashWindowRecord(IBinder appToken, View decorView,
+ @StartingWindowInfo.StartingWindowType int suggestType) {
+ mAppToken = appToken;
+ mRootView = decorView;
+ mSuggestType = suggestType;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ void setSplashScreenView(@Nullable SplashScreenView splashScreenView) {
+ if (mSetSplashScreen) {
+ return;
+ }
+ mSplashView = splashScreenView;
+ mBGColor = mSplashView != null ? mSplashView.getInitBackgroundColor()
+ : Color.TRANSPARENT;
+ mSetSplashScreen = true;
+ }
+
+ void parseAppSystemBarColor(Context context) {
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ mDrawsSystemBarBackgrounds = a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
+ if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+ }
+ if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+ }
+ a.recycle();
+ }
+
+ // Reset the system bar color which set by splash screen, make it align to the app.
+ void clearSystemBarColor() {
+ if (mRootView == null || !mRootView.isAttachedToWindow()) {
+ return;
+ }
+ if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
+ final WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mRootView.getLayoutParams();
+ if (mDrawsSystemBarBackgrounds) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ } else {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ mRootView.setLayoutParams(lp);
+ }
+ mRootView.getWindowInsetsController().setSystemBarsAppearance(
+ mSystemBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (mRootView == null) {
+ return;
+ }
+ if (mSplashView == null) {
+ // shouldn't happen, the app window may be drawn earlier than starting window?
+ Slog.e(TAG, "Found empty splash screen, remove!");
+ removeWindowInner(mRootView, false);
+ return;
+ }
+ clearSystemBarColor();
+ if (immediately
+ || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ removeWindowInner(mRootView, false);
+ } else {
+ if (info.playRevealAnimation) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ () -> removeWindowInner(mRootView, true),
+ mCreateTime, info.roundedCornerRadius);
+ } else {
+ // the SplashScreenView has been copied to client, hide the view to skip
+ // default exit animation
+ removeWindowInner(mRootView, true);
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 22e804547d5c..ff06db370d1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,172 +16,80 @@
package com.android.wm.shell.startingsurface;
-import static android.content.Context.CONTEXT_RESTRICTED;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
+import android.annotation.CallSuper;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Slog;
import android.util.SparseArray;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsetsController;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.widget.FrameLayout;
+import android.view.WindowlessWindowManager;
import android.window.SplashScreenView;
-import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ContrastColorUtil;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.function.Supplier;
-
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
- *
- * In order to speed up, there will use two threads to creating a splash screen in parallel.
- * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
- * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
- * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
- * can synchronize on each frame.
- *
- * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
- * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
- * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
- * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
- * quickly.
- *
- * So basically we are using the spare time to prepare the SplashScreenView while splash screen
- * thread is waiting for
- * 1. WindowManager#addView(binder call to WM),
- * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
- * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
- * always happen before #draw).
- * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
- * splash-screen background tread can make they execute in parallel, which ensure it is faster then
- * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
- *
- * Here is the sequence to compare the difference between using single and two thread.
- *
- * Single thread:
- * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
- * -> draw -> AdaptiveIconDrawable#draw
- *
- * Two threads:
- * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
- * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
- * directly).
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
- private static final String TAG = StartingWindowController.TAG;
- private final Context mContext;
- private final DisplayManager mDisplayManager;
private final ShellExecutor mSplashScreenExecutor;
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
- private Choreographer mChoreographer;
- private final WindowManagerGlobal mWindowManagerGlobal;
- private StartingSurface.SysuiProxy mSysuiProxy;
- private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
-
- private static final int LIGHT_BARS_MASK =
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- /**
- * The minimum duration during which the splash screen is shown when the splash screen icon is
- * animated.
- */
- static final long MINIMAL_ANIMATION_DURATION = 400L;
-
- /**
- * Allow the icon style splash screen to be displayed for longer to give time for the animation
- * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
- * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
- */
- static final long TIME_WINDOW_DURATION = 100L;
-
- /**
- * The maximum duration during which the splash screen will be shown if the application is ready
- * to show before the icon animation finishes.
- */
- static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+ @VisibleForTesting
+ final SplashscreenWindowCreator mSplashscreenWindowCreator;
+ private final SnapshotWindowCreator mSnapshotWindowCreator;
+ private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
+ private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
+ // Windowless surface could co-exist with starting window in a task.
+ @VisibleForTesting
+ final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
IconProvider iconProvider, TransactionPool pool) {
- mContext = context;
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
- mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
- mWindowManagerGlobal = WindowManagerGlobal.getInstance();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- }
-
- @VisibleForTesting
- final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
-
- /**
- * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
- * rendered and that have not yet been removed by their client.
- */
- private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
- new SparseArray<>(1);
-
- private Display getDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId);
- }
-
- int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
- return splashScreenThemeResId != 0
- ? splashScreenThemeResId
- : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
- : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
+ displayManager.getDisplay(DEFAULT_DISPLAY);
+
+ mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
+ context, splashScreenExecutor, displayManager, mWindowRecords);
+ mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
+ mWindowRecords);
+ mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
+ mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
+ mWindowlessRecords, pool);
+ mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
+ mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
}
void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
- mSysuiProxy = sysuiProxy;
+ mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
+ mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
}
/**
@@ -189,328 +97,55 @@ public class StartingSurfaceDrawer {
*
* @param suggestType The suggestion type to draw the splash screen.
*/
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
- final RunningTaskInfo taskInfo = windowInfo.taskInfo;
- final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
- ? windowInfo.targetActivityInfo
- : taskInfo.topActivityInfo;
- if (activityInfo == null || activityInfo.packageName == null) {
- return;
- }
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
-
- // replace with the default theme if the application didn't set
- final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- 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.
- return;
- }
- Context context = displayId == DEFAULT_DISPLAY
- ? mContext : mContext.createDisplayContext(display);
- if (context == null) {
- return;
- }
- if (theme != context.getThemeResId()) {
- try {
- context = context.createPackageContextAsUser(activityInfo.packageName,
- CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
- context.setTheme(theme);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + activityInfo.packageName + " for user " + taskInfo.userId, e);
- return;
- }
- }
-
- final Configuration taskConfig = taskInfo.getConfiguration();
- if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
- 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(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- try {
- if (resId != 0 && overrideContext.getDrawable(resId) != null) {
- // 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.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: apply overrideConfig %s",
- taskConfig);
- context = overrideContext;
- }
- } catch (Resources.NotFoundException e) {
- Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
- + taskId, e);
- return;
- }
- typedArray.recycle();
- }
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
- params.setFitInsetsSides(0);
- params.setFitInsetsTypes(0);
- params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
- int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- } else {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- params.layoutInDisplayCutoutMode);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
-
- // Assumes it's safe to show starting windows of launched apps while
- // the keyguard is being hidden. This is okay because starting windows never show
- // secret information.
- // TODO(b/113840485): Occluded may not only happen on default display
- if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
- }
-
- // Force the window flags: this is a fake window, so it is not really
- // 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;
- params.flags = windowFlags;
- params.token = appToken;
- params.packageName = activityInfo.packageName;
- params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
- params.setTitle("Splash Screen " + activityInfo.packageName);
-
- // TODO(b/173975965) tracking performance
- // Prepare the splash screen content view on splash screen worker thread in parallel, so the
- // content view won't be blocked by binder call like addWindow and relayout.
- // 1. Trigger splash screen worker thread to create SplashScreenView before/while
- // Session#addWindow.
- // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
- // traversal, which will call Session#relayout on splash screen thread.
- // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
- // the same time the splash screen thread should be executing Session#relayout. Blocking the
- // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
-
- // Record whether create splash screen view success, notify to current thread after
- // create splash screen view finished.
- final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(context));
- rootLayout.setPadding(0, 0, 0, 0);
- rootLayout.setFitsSystemWindows(false);
- final Runnable setViewSynchronized = () -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
- // waiting for setContentView before relayoutWindow
- SplashScreenView contentView = viewSupplier.get();
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- // If record == null, either the starting window added fail or removed already.
- // Do not add this view if the token is mismatch.
- if (record != null && appToken == record.mAppToken) {
- // if view == null then creation of content view was failed.
- if (contentView != null) {
- try {
- rootLayout.addView(contentView);
- } catch (RuntimeException e) {
- Slog.w(TAG, "failed set content view to starting window "
- + "at taskId: " + taskId, e);
- contentView = null;
- }
- }
- record.setSplashScreenView(contentView);
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- };
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(true, TAG);
- }
- mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
- viewSupplier::setView, viewSupplier::setUiThreadInitTask);
- try {
- if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
- // We use the splash screen worker thread to create SplashScreenView while adding
- // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
- // And since Choreographer#doFrame won't happen immediately after adding the window,
- // if the view is not added to the PhoneWindow on the first #doFrame, the view will
- // not be rendered on the first frame. So here we need to synchronize the view on
- // the window before first round relayoutWindow, which will happen after insets
- // animation.
- mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- record.parseAppSystemBarColor(context);
- // Block until we get the background color.
- final SplashScreenView contentView = viewSupplier.get();
- if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- contentView.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- final int lightBarAppearance = ContrastColorUtil.isColorLight(
- contentView.getInitBackgroundColor())
- ? LIGHT_BARS_MASK : 0;
- contentView.getWindowInsetsController().setSystemBarsAppearance(
- lightBarAppearance, LIGHT_BARS_MASK);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
- }
- record.mBGColor = contentView.getInitBackgroundColor();
- } else {
- // release the icon view host
- final SplashScreenView contentView = viewSupplier.get();
- if (contentView.getSurfaceHost() != null) {
- SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
- }
- }
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
- }
+ mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
int getStartingWindowBackgroundColorForTask(int taskId) {
- final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
+ final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
if (startingWindowRecord == null) {
return Color.TRANSPARENT;
}
- return startingWindowRecord.mBGColor;
- }
-
- private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
- private SplashScreenView mView;
- private boolean mIsViewSet;
- private Runnable mUiThreadInitTask;
- void setView(SplashScreenView view) {
- synchronized (this) {
- mView = view;
- mIsViewSet = true;
- notify();
- }
- }
-
- void setUiThreadInitTask(Runnable initTask) {
- synchronized (this) {
- mUiThreadInitTask = initTask;
- }
- }
-
- @Override
- @Nullable
- public SplashScreenView get() {
- synchronized (this) {
- while (!mIsViewSet) {
- try {
- wait();
- } catch (InterruptedException ignored) {
- }
- }
- if (mUiThreadInitTask != null) {
- mUiThreadInitTask.run();
- mUiThreadInitTask = null;
- }
- return mView;
- }
- }
+ return startingWindowRecord.getBGColor();
}
int estimateTaskBackgroundColor(TaskInfo taskInfo) {
- if (taskInfo.topActivityInfo == null) {
- return Color.TRANSPARENT;
- }
- final ActivityInfo activityInfo = taskInfo.topActivityInfo;
- final String packageName = activityInfo.packageName;
- final int userId = taskInfo.userId;
- final Context windowContext;
- try {
- windowContext = mContext.createPackageContextAsUser(
- packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + packageName + " for user " + taskInfo.userId, e);
- return Color.TRANSPARENT;
- }
- try {
- final IPackageManager packageManager = ActivityThread.getPackageManager();
- final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
- userId);
- final int splashScreenThemeId = splashScreenThemeName != null
- ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
- : 0;
-
- final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
-
- if (theme != windowContext.getThemeResId()) {
- windowContext.setTheme(theme);
- }
- return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
- } catch (RuntimeException | RemoteException e) {
- Slog.w(TAG, "failed get starting window background color at taskId: "
- + taskInfo.taskId, e);
- }
- return Color.TRANSPARENT;
+ return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
}
/**
* Called when a task need a snapshot starting window.
*/
- void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
- TaskSnapshot snapshot) {
- final int taskId = startingWindowInfo.taskInfo.taskId;
- // Remove any existing starting window for this task before adding.
- removeWindowNoAnimate(taskId);
- final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
- snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
- if (surface == null) {
- return;
- }
- final StartingWindowRecord tView = new StartingWindowRecord(appToken,
- null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
- mStartingWindowRecords.put(taskId, tView);
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+ mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Task start finish, remove starting surface for task: %d",
- removalInfo.taskId);
- removeWindowSynced(removalInfo, false /* immediately */);
+ if (removalInfo.windowlessSurface) {
+ mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Task start finish, remove starting surface for task: %d",
+ removalInfo.taskId);
+ mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+ }
+ }
+
+ /**
+ * Create a windowless starting surface and attach to the root surface.
+ */
+ void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
+ if (windowInfo.taskSnapshot != null) {
+ mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
+ windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
+ } else {
+ mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
+ windowInfo, windowInfo.rootSurface);
+ }
}
/**
@@ -519,37 +154,15 @@ public class StartingSurfaceDrawer {
public void clearAllWindows() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Clear all starting windows immediately");
- final int taskSize = mStartingWindowRecords.size();
- final int[] taskIds = new int[taskSize];
- for (int i = taskSize - 1; i >= 0; --i) {
- taskIds[i] = mStartingWindowRecords.keyAt(i);
- }
- for (int i = taskSize - 1; i >= 0; --i) {
- removeWindowNoAnimate(taskIds[i]);
- }
+ mWindowRecords.clearAllWindows();
+ mWindowlessRecords.clearAllWindows();
}
/**
* Called when the Task wants to copy the splash screen.
*/
public void copySplashScreenView(int taskId) {
- final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
- SplashScreenViewParcelable parcelable;
- SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
- if (splashScreenView != null && splashScreenView.isCopyable()) {
- parcelable = new SplashScreenViewParcelable(splashScreenView);
- parcelable.setClientCallback(
- new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
- () -> onAppSplashScreenViewRemoved(taskId, false))));
- splashScreenView.onCopied();
- mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
- } else {
- parcelable = null;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Copying splash screen window view for task: %d with parcelable %b",
- taskId, parcelable != null);
- ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ mSplashscreenWindowCreator.copySplashScreenView(taskId);
}
/**
@@ -559,195 +172,148 @@ public class StartingSurfaceDrawer {
* @param taskId The Task id on which the splash screen was attached
*/
public void onAppSplashScreenViewRemoved(int taskId) {
- onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
}
- /**
- * @param fromServer If true, this means the removal was notified by the server. This is only
- * used for debugging purposes.
- * @see #onAppSplashScreenViewRemoved(int)
- */
- private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
- SurfaceControlViewHost viewHost =
- mAnimatedSplashScreenSurfaceHosts.get(taskId);
- if (viewHost == null) {
- return;
+ void onImeDrawnOnTask(int taskId) {
+ onImeDrawnOnTask(mWindowRecords, taskId);
+ onImeDrawnOnTask(mWindowlessRecords, taskId);
+ }
+
+ private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
+ final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+ records.getRecord(taskId);
+ final SnapshotRecord record = sRecord instanceof SnapshotRecord
+ ? (SnapshotRecord) sRecord : null;
+ if (record != null && record.hasImeSurface()) {
+ records.removeWindow(taskId, true);
}
- mAnimatedSplashScreenSurfaceHosts.remove(taskId);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
- fromServer ? "Server cleaned up" : "App removed", taskId);
- SplashScreenView.releaseIconHost(viewHost);
}
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
- boolean shouldSaveView = true;
- final Context context = view.getContext();
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
- mWindowManagerGlobal.addView(view, params, display,
- null /* parentWindow */, context.getUserId());
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- mWindowManagerGlobal.removeView(view, true /* immediate */);
- shouldSaveView = false;
+ static class WindowlessStartingWindow extends WindowlessWindowManager {
+ SurfaceControl mChildSurface;
+
+ WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
+ super(c, rootSurface, null /* hostInputToken */);
+ }
+
+ @Override
+ protected SurfaceControl getParentSurface(IWindow window,
+ WindowManager.LayoutParams attrs) {
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("Windowless window")
+ .setHidden(false)
+ .setParent(mRootSurface)
+ .setCallsite("WindowlessStartingWindow#attachToParentSurface");
+ mChildSurface = builder.build();
+ try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+ t.setLayer(mChildSurface, Integer.MAX_VALUE);
+ t.apply();
}
+ return mChildSurface;
}
- if (shouldSaveView) {
- removeWindowNoAnimate(taskId);
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ abstract static class StartingWindowRecord {
+ protected int mBGColor;
+ abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+ int getBGColor() {
+ return mBGColor;
}
- return shouldSaveView;
}
- @VisibleForTesting
- void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
- @StartingWindowType int suggestType) {
- final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
- null/* TaskSnapshotWindow */, suggestType);
- mStartingWindowRecords.put(taskId, tView);
- }
+ abstract static class SnapshotRecord extends StartingWindowRecord {
+ private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME
+ * visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+ private final Runnable mScheduledRunnable = this::removeImmediately;
- private void removeWindowNoAnimate(int taskId) {
- mTmpRemovalInfo.taskId = taskId;
- removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
- }
+ @WindowConfiguration.ActivityType protected final int mActivityType;
+ protected final ShellExecutor mRemoveExecutor;
- void onImeDrawnOnTask(int taskId) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null && record.mTaskSnapshotWindow != null
- && record.mTaskSnapshotWindow.hasImeSurface()) {
- removeWindowNoAnimate(taskId);
+ SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
+ mActivityType = activityType;
+ mRemoveExecutor = removeExecutor;
}
- }
-
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
- final int taskId = removalInfo.taskId;
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- if (record.mDecorView != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing splash screen window for task: %d", taskId);
- if (record.mContentView != null) {
- record.clearSystemBarColor();
- if (immediately
- || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- removeWindowInner(record.mDecorView, false);
- } else {
- if (removalInfo.playRevealAnimation) {
- mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
- removalInfo.windowAnimationLeash, removalInfo.mainFrame,
- () -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime, removalInfo.roundedCornerRadius);
- } else {
- // the SplashScreenView has been copied to client, hide the view to skip
- // default exit animation
- removeWindowInner(record.mDecorView, true);
- }
- }
- } else {
- // shouldn't happen
- Slog.e(TAG, "Found empty splash screen, remove!");
- removeWindowInner(record.mDecorView, false);
- }
+ @Override
+ public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (immediately) {
+ removeImmediately();
+ } else {
+ scheduleRemove(info.deferRemoveForIme);
}
- if (record.mTaskSnapshotWindow != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Removing task snapshot window for %d", taskId);
- if (immediately) {
- record.mTaskSnapshotWindow.removeImmediately();
- } else {
- record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
- }
- }
- mStartingWindowRecords.remove(taskId);
}
- }
- private void removeWindowInner(View decorView, boolean hideView) {
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(false, TAG);
+ void scheduleRemove(boolean deferRemoveForIme) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
+ return;
+ }
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+ final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
+ ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+ : DELAY_REMOVAL_TIME_GENERAL;
+ mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Defer removing snapshot surface in %d", delayRemovalTime);
}
- if (hideView) {
- decorView.setVisibility(View.GONE);
+
+ protected abstract boolean hasImeSurface();
+
+ @CallSuper
+ protected void removeImmediately() {
+ mRemoveExecutor.removeCallbacks(mScheduledRunnable);
}
- mWindowManagerGlobal.removeView(decorView, false /* immediate */);
}
- /**
- * Record the view or surface for a starting window.
- */
- private static class StartingWindowRecord {
- private final IBinder mAppToken;
- private final View mDecorView;
- private final TaskSnapshotWindow mTaskSnapshotWindow;
- private SplashScreenView mContentView;
- private boolean mSetSplashScreen;
- @StartingWindowType private int mSuggestType;
- private int mBGColor;
- private final long mCreateTime;
- private int mSystemBarAppearance;
- private boolean mDrawsSystemBarBackgrounds;
-
- StartingWindowRecord(IBinder appToken, View decorView,
- TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
- mAppToken = appToken;
- mDecorView = decorView;
- mTaskSnapshotWindow = taskSnapshotWindow;
- if (mTaskSnapshotWindow != null) {
- mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ static class StartingWindowRecordManager {
+ private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+ private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
+ new SparseArray<>();
+
+ void clearAllWindows() {
+ final int taskSize = mStartingWindowRecords.size();
+ final int[] taskIds = new int[taskSize];
+ for (int i = taskSize - 1; i >= 0; --i) {
+ taskIds[i] = mStartingWindowRecords.keyAt(i);
+ }
+ for (int i = taskSize - 1; i >= 0; --i) {
+ removeWindow(taskIds[i], true);
}
- mSuggestType = suggestType;
- mCreateTime = SystemClock.uptimeMillis();
}
- private void setSplashScreenView(SplashScreenView splashScreenView) {
- if (mSetSplashScreen) {
- return;
- }
- mContentView = splashScreenView;
- mSetSplashScreen = true;
+ void addRecord(int taskId, StartingWindowRecord record) {
+ mStartingWindowRecords.put(taskId, record);
}
- private void parseAppSystemBarColor(Context context) {
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- mDrawsSystemBarBackgrounds = a.getBoolean(
- R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
- if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- }
- if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+ void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
+ final int taskId = removeInfo.taskId;
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.removeIfPossible(removeInfo, immediately);
+ mStartingWindowRecords.remove(taskId);
}
- a.recycle();
}
- // Reset the system bar color which set by splash screen, make it align to the app.
- private void clearSystemBarColor() {
- if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
- return;
- }
- if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
- final WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
- if (mDrawsSystemBarBackgrounds) {
- lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- } else {
- lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- mDecorView.setLayoutParams(lp);
- }
- mDecorView.getWindowInsetsController().setSystemBarsAppearance(
- mSystemBarAppearance, LIGHT_BARS_MASK);
+ void removeWindow(int taskId, boolean immediately) {
+ mTmpRemovalInfo.taskId = taskId;
+ removeWindow(mTmpRemovalInfo, immediately);
+ }
+
+ StartingWindowRecord getRecord(int taskId) {
+ return mStartingWindowRecords.get(taskId);
+ }
+
+ @VisibleForTesting
+ int recordSize() {
+ return mStartingWindowRecords.size();
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index be2e79342d07..bec4ba3bf0d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -21,6 +21,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
@@ -29,7 +30,6 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Color;
-import android.os.IBinder;
import android.os.Trace;
import android.util.SparseIntArray;
import android.window.StartingWindowInfo;
@@ -152,22 +152,23 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
/**
* Called when a task need a starting window.
*/
- public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ public void addStartingWindow(StartingWindowInfo windowInfo) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (isSplashScreenType(suggestionType)) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
- suggestionType);
+ if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
+ mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo);
+ } else if (isSplashScreenType(suggestionType)) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = windowInfo.taskSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
- snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
}
- if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
+ if (suggestionType != STARTING_WINDOW_TYPE_NONE
+ && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
int taskId = runningTaskInfo.taskId;
int color = mStartingSurfaceDrawer
.getStartingWindowBackgroundColorForTask(taskId);
@@ -218,11 +219,13 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
removalInfo));
- mSplashScreenExecutor.executeDelayed(() -> {
- synchronized (mTaskBackgroundColors) {
- mTaskBackgroundColors.delete(removalInfo.taskId);
- }
- }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ if (!removalInfo.windowlessSurface) {
+ mSplashScreenExecutor.executeDelayed(() -> {
+ synchronized (mTaskBackgroundColors) {
+ mTaskBackgroundColors.delete(removalInfo.taskId);
+ }
+ }, TASK_BG_COLOR_RETAIN_TIME_MS);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 7b498e4f54ec..c964df1452e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,52 +16,17 @@
package com.android.wm.shell.startingsurface;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
-import static android.graphics.Color.alpha;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
-import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
-
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityThread;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
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;
@@ -73,19 +38,14 @@ import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
+import android.window.SnapshotDrawerUtils;
import android.window.StartingWindowInfo;
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;
@@ -99,59 +59,17 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class TaskSnapshotWindow {
- /**
- * When creating the starting window, we use the exact same layout flags such that we end up
- * with a window with the exact same dimensions etc. However, these flags are not used in layout
- * and might cause other side effects so we exclude them.
- */
- static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE
- | FLAG_NOT_TOUCH_MODAL
- | FLAG_ALT_FOCUSABLE_IM
- | FLAG_NOT_FOCUSABLE
- | FLAG_HARDWARE_ACCELERATED
- | FLAG_IGNORE_CHEEK_PRESSES
- | FLAG_LOCAL_FOCUS_MODE
- | FLAG_SLIPPERY
- | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH
- | FLAG_SCALED
- | FLAG_SECURE;
-
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;
- /**
- * The max delay time in milliseconds for removing the task snapshot window with IME visible.
- * Ideally the delay time will be shorter when receiving
- * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
- */
- private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+ private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
- private final SurfaceControl mSurfaceControl;
private final IWindowSession mSession;
- private final Rect mTaskBounds;
- private final Rect mFrame = new Rect();
- private final Rect mSystemBarInsets = new Rect();
- private TaskSnapshot mSnapshot;
- private final RectF mTmpSnapshotSize = new RectF();
- private final RectF mTmpDstFrame = new RectF();
- private final CharSequence mTitle;
private boolean mHasDrawn;
- private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
- private final int mActivityType;
- private final int mStatusBarColor;
- private final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
private final int mOrientationOnCreation;
- private final SurfaceControl.Transaction mTransaction;
- private final Matrix mSnapshotMatrix = new Matrix();
- private final float[] mTmpFloat9 = new float[9];
- private final Runnable mScheduledRunnable = this::removeImmediately;
+
private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
@@ -162,68 +80,34 @@ public class TaskSnapshotWindow {
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;
final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
- if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
- Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId);
+
+ final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters(
+ info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING,
+ snapshot.getHardwareBuffer().getFormat(), appToken);
+ if (layoutParams == null) {
+ Slog.e(TAG, "TaskSnapshotWindow no layoutParams");
return null;
}
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
-
- final int appearance = attrs.insetsFlags.appearance;
- final int windowFlags = attrs.flags;
- final int windowPrivateFlags = attrs.privateFlags;
-
- layoutParams.packageName = mainWindowParams.packageName;
- layoutParams.windowAnimations = mainWindowParams.windowAnimations;
- layoutParams.dimAmount = mainWindowParams.dimAmount;
- layoutParams.type = TYPE_APPLICATION_STARTING;
- layoutParams.format = snapshot.getHardwareBuffer().getFormat();
- layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
- | FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
- | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
- layoutParams.token = appToken;
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.insetsFlags.appearance = appearance;
- layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
- layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
- layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
- layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
- layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
-
- layoutParams.setTitle(String.format(TITLE_FORMAT, taskId));
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
final int orientation = snapshot.getOrientation();
- final int activityType = runningTaskInfo.topActivityType;
final int displayId = runningTaskInfo.displayId;
final IWindowSession session = WindowManagerGlobal.getWindowSession();
final SurfaceControl surfaceControl = new SurfaceControl();
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
- final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
- final TaskDescription taskDescription;
- if (runningTaskInfo.taskDescription != null) {
- taskDescription = runningTaskInfo.taskDescription;
- } else {
- taskDescription = new TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
+ final TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
- windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
+ snapshot, taskDescription, orientation,
+ clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
final InsetsState tmpInsetsState = new InsetsState();
@@ -233,7 +117,7 @@ public class TaskSnapshotWindow {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
+ info.requestedVisibleTypes, tmpInputChannel, tmpInsetsState, tmpControls,
new Rect(), sizeCompatScale);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
@@ -252,35 +136,28 @@ public class TaskSnapshotWindow {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
+ Slog.w(TAG, "Failed to relayout snapshot starting window");
+ return null;
}
- final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState);
- snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets);
- snapshotSurface.drawSnapshot();
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
+ taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */);
+ snapshotSurface.mHasDrawn = true;
+ snapshotSurface.reportDrawn();
+
return snapshotSurface;
}
- public TaskSnapshotWindow(SurfaceControl surfaceControl,
- TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
- int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, InsetsState topWindowInsetsState,
- Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
+ public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
+ int currentOrientation, Runnable clearWindowHandler,
+ ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = new Window();
mWindow.setSession(mSession);
- mSurfaceControl = surfaceControl;
- mSnapshot = snapshot;
- mTitle = title;
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
- mTaskBounds = taskBounds;
- mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState);
- mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
- mActivityType = activityType;
- mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -293,40 +170,7 @@ public class TaskSnapshotWindow {
return mHasImeSurface;
}
- /**
- * Ask system bar background painter to draw status bar background.
- * @hide
- */
- public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
- mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
- }
-
- /**
- * Ask system bar background painter to draw navigation bar background.
- * @hide
- */
- public void drawNavigationBarBackground(Canvas c) {
- mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
- }
-
- void scheduleRemove(boolean deferRemoveForIme) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
- final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
- ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
- mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Defer removing snapshot surface in %d", delayRemovalTime);
- }
-
void removeImmediately() {
- mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
@@ -337,178 +181,6 @@ public class TaskSnapshotWindow {
}
/**
- * Set frame size.
- * @hide
- */
- public void setFrames(Rect frame, Rect systemBarInsets) {
- mFrame.set(frame);
- mSystemBarInsets.set(systemBarInsets);
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- mSizeMismatch = (mFrame.width() != snapshot.getWidth()
- || mFrame.height() != snapshot.getHeight());
- mSystemBarBackgroundPainter.setInsets(systemBarInsets);
- }
-
- static Rect getSystemBarInsets(Rect frame, InsetsState state) {
- return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
- false /* ignoreVisibility */).toRect();
- }
-
- private void drawSnapshot() {
- 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
- // window with the background color!
- drawSizeMismatchSnapshot();
- } else {
- drawSizeMatchSnapshot();
- }
- mHasDrawn = true;
- reportDrawn();
-
- // In case window manager leaks us, make sure we don't retain the snapshot.
- if (mSnapshot.getHardwareBuffer() != null) {
- mSnapshot.getHardwareBuffer().close();
- }
- mSnapshot = null;
- mSurfaceControl.release();
- }
-
- private void drawSizeMatchSnapshot() {
- mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
- .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
- .apply();
- }
-
- private void drawSizeMismatchSnapshot() {
- final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
- final SurfaceSession session = new SurfaceSession();
-
- // We consider nearly matched dimensions as there can be rounding errors and the user won't
- // notice very minute differences from scaling one dimension more than the other
- final boolean aspectRatioMismatch = Math.abs(
- ((float) buffer.getWidth() / buffer.getHeight())
- - ((float) mFrame.width() / mFrame.height())) > 0.01f;
-
- // Keep a reference to it such that it doesn't get destroyed when finalized.
- SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
- .setName(mTitle + " - task-snapshot-surface")
- .setBLASTLayer()
- .setFormat(buffer.getFormat())
- .setParent(mSurfaceControl)
- .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
- .build();
-
- final Rect frame;
- // We can just show the surface here as it will still be hidden as the parent is
- // still hidden.
- mTransaction.show(childSurfaceControl);
- if (aspectRatioMismatch) {
- // Clip off ugly navigation bar.
- final Rect crop = calculateSnapshotCrop();
- frame = calculateSnapshotFrame(crop);
- mTransaction.setWindowCrop(childSurfaceControl, crop);
- mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
- mTmpSnapshotSize.set(crop);
- mTmpDstFrame.set(frame);
- } else {
- frame = null;
- mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
- mTmpDstFrame.set(mFrame);
- mTmpDstFrame.offsetTo(0, 0);
- }
-
- // Scale the mismatch dimensions to fill the task bounds
- mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
- mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
- mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
- 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,
- HardwareBuffer.createFromGraphicBuffer(background));
- }
- mTransaction.apply();
- childSurfaceControl.release();
- }
-
- /**
- * Calculates the snapshot crop in snapshot coordinate space.
- *
- * @return crop rect in snapshot coordinate space.
- */
- public Rect calculateSnapshotCrop() {
- final Rect rect = new Rect();
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
- final Rect insets = mSnapshot.getContentInsets();
-
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Let's remove all system decorations except the status bar, but only if the task is at the
- // very top of the screen.
- final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
- rect.inset((int) (insets.left * scaleX),
- isTop ? 0 : (int) (insets.top * scaleY),
- (int) (insets.right * scaleX),
- (int) (insets.bottom * scaleY));
- return rect;
- }
-
- /**
- * Calculates the snapshot frame in window coordinate space from crop.
- *
- * @param crop rect that is in snapshot coordinate space.
- */
- public Rect calculateSnapshotFrame(Rect crop) {
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Rescale the frame from snapshot to window coordinate space
- final Rect frame = new Rect(0, 0,
- (int) (crop.width() / scaleX + 0.5f),
- (int) (crop.height() / scaleY + 0.5f)
- );
-
- // However, we also need to make space for the navigation bar on the left side.
- frame.offset(mSystemBarInsets.left, 0);
- return frame;
- }
-
- /**
- * Draw status bar and navigation bar background.
- * @hide
- */
- public void drawBackgroundAndBars(Canvas c, Rect frame) {
- final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
- final boolean fillHorizontally = c.getWidth() > frame.right;
- final boolean fillVertically = c.getHeight() > frame.bottom;
- if (fillHorizontally) {
- c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
- c.getWidth(), fillVertically
- ? frame.bottom
- : c.getHeight(),
- mBackgroundPaint);
- }
- if (fillVertically) {
- c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
- }
- mSystemBarBackgroundPainter.drawDecors(c, frame);
- }
-
- /**
* Clear window from drawer, must be post on main executor.
*/
private void clearWindowSynced() {
@@ -535,7 +207,7 @@ public class TaskSnapshotWindow {
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
- int resizeMode) {
+ boolean dragResizing) {
final TaskSnapshotWindow snapshot = mOuter.get();
if (snapshot == null) {
return;
@@ -556,91 +228,4 @@ public class TaskSnapshotWindow {
});
}
}
-
- /**
- * Helper class to draw the background of the system bars in regions the task snapshot isn't
- * filling the window.
- */
- static class SystemBarBackgroundPainter {
- private final Paint mStatusBarPaint = new Paint();
- private final Paint mNavigationBarPaint = new Paint();
- private final int mStatusBarColor;
- private final int mNavigationBarColor;
- private final int mWindowFlags;
- private final int mWindowPrivateFlags;
- private final float mScale;
- private final InsetsState mInsetsState;
- private final Rect mSystemBarInsets = new Rect();
-
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- TaskDescription taskDescription, float scale, InsetsState insetsState) {
- mWindowFlags = windowFlags;
- mWindowPrivateFlags = windowPrivateFlags;
- mScale = scale;
- final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
- final int semiTransparent = context.getColor(
- R.color.system_bar_background_semi_transparent);
- mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
- semiTransparent, taskDescription.getStatusBarColor(), appearance,
- APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
- mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
- FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), appearance,
- APPEARANCE_LIGHT_NAVIGATION_BARS,
- taskDescription.getEnsureNavigationBarContrastWhenTransparent()
- && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
- mStatusBarPaint.setColor(mStatusBarColor);
- mNavigationBarPaint.setColor(mNavigationBarColor);
- mInsetsState = insetsState;
- }
-
- void setInsets(Rect systemBarInsets) {
- mSystemBarInsets.set(systemBarInsets);
- }
-
- int getStatusBarColorViewHeight() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
- return (int) (mSystemBarInsets.top * mScale);
- } else {
- return 0;
- }
- }
-
- private boolean isNavigationBarColorViewVisible() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
- }
-
- void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
- drawNavigationBarBackground(c);
- }
-
- void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
- int statusBarHeight) {
- if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
- && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
- final int rightInset = (int) (mSystemBarInsets.right * mScale);
- final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
- c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
- }
- }
-
- @VisibleForTesting
- void drawNavigationBarBackground(Canvas c) {
- final Rect navigationBarRect = new Rect();
- getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
- mScale);
- final boolean visible = isNavigationBarColorViewVisible();
- if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
- c.drawRect(navigationBarRect, mNavigationBarPaint);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
new file mode 100644
index 000000000000..144547885501
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SnapshotDrawerUtils;
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSnapshotWindowCreator {
+ private static final int DEFAULT_FADEOUT_DURATION = 233;
+ private final StartingSurfaceDrawer.StartingWindowRecordManager
+ mStartingWindowRecordManager;
+ private final DisplayManager mDisplayManager;
+ private final Context mContext;
+ private final SplashscreenContentDrawer mSplashscreenContentDrawer;
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSnapshotWindowCreator(
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ Context context,
+ DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer,
+ TransactionPool transactionPool) {
+ mStartingWindowRecordManager = startingWindowRecordManager;
+ mContext = context;
+ mDisplayManager = displayManager;
+ mSplashscreenContentDrawer = splashscreenContentDrawer;
+ mTransactionPool = transactionPool;
+ }
+
+ void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface,
+ TaskSnapshot snapshot, ShellExecutor removeExecutor) {
+ final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+ final int taskId = runningTaskInfo.taskId;
+ final String title = "Windowless Snapshot " + taskId;
+ final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters(
+ info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(),
+ null /* token */);
+ if (lp == null) {
+ return;
+ }
+ final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ runningTaskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
+ mContext, display, wlw, "WindowlessSnapshotWindowCreator");
+ final Point taskSize = snapshot.getTaskSize();
+ final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y);
+ final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
+ final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ mViewHost.setView(rootLayout, lp);
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot,
+ snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */);
+
+ final ActivityManager.TaskDescription taskDescription =
+ SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
+
+ final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
+ taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
+ runningTaskInfo.topActivityType, removeExecutor);
+ mStartingWindowRecordManager.addRecord(taskId, record);
+ info.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mChildSurface;
+ private final boolean mHasImeSurface;
+
+ SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
+ int bgColor, boolean hasImeSurface, int activityType,
+ ShellExecutor removeExecutor) {
+ super(activityType, removeExecutor);
+ mViewHost = viewHost;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mHasImeSurface = hasImeSurface;
+ }
+
+ @Override
+ protected void removeImmediately() {
+ super.removeImmediately();
+ fadeoutThenRelease();
+ }
+
+ void fadeoutThenRelease() {
+ final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f);
+ fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION);
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ fadeOutAnimator.addUpdateListener(animation -> {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ return;
+ }
+ t.setAlpha(mChildSurface, (float) animation.getAnimatedValue());
+ t.apply();
+ });
+
+ fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mChildSurface == null || !mChildSurface.isValid()) {
+ fadeOutAnimator.cancel();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransactionPool.release(t);
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ });
+ fadeOutAnimator.start();
+ }
+
+ @Override
+ protected boolean hasImeSurface() {
+ return mHasImeSurface;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
new file mode 100644
index 000000000000..12a0d4054b4d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.startingsurface;
+
+import static android.graphics.Color.WHITE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
+
+ private final TransactionPool mTransactionPool;
+
+ WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer,
+ Context context,
+ ShellExecutor splashScreenExecutor,
+ DisplayManager displayManager,
+ StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+ TransactionPool pool) {
+ super(contentDrawer, context, splashScreenExecutor, displayManager,
+ startingWindowRecordManager);
+ mTransactionPool = pool;
+ }
+
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return;
+ }
+ final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
+ 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+ if (myContext == null) {
+ return;
+ }
+ final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+ new StartingSurfaceDrawer.WindowlessStartingWindow(
+ taskInfo.configuration, rootSurface);
+ final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
+ myContext, display, wlw, "WindowlessSplashWindowCreator");
+ final String title = "Windowless Splash " + taskInfo.taskId;
+ final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters(
+ myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title,
+ PixelFormat.TRANSLUCENT, new Binder());
+ final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ lp.width = windowBounds.width();
+ lp.height = windowBounds.height();
+ final ActivityManager.TaskDescription taskDescription;
+ if (taskInfo.taskDescription != null) {
+ taskDescription = taskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+ viewHost.setView(rootLayout, lp);
+
+ final int bgColor = taskDescription.getBackgroundColor();
+ final SplashScreenView splashScreenView = mSplashscreenContentDrawer
+ .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
+ rootLayout.addView(splashScreenView);
+ final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView,
+ wlw.mChildSurface, bgColor);
+ mStartingWindowRecordManager.addRecord(taskInfo.taskId, record);
+ windowInfo.notifyAddComplete(wlw.mChildSurface);
+ }
+
+ private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+ private SurfaceControlViewHost mViewHost;
+ private final long mCreateTime;
+ private SurfaceControl mChildSurface;
+ private final SplashScreenView mSplashView;
+
+ SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView,
+ SurfaceControl childSurface, int bgColor) {
+ mViewHost = viewHost;
+ mSplashView = splashView;
+ mChildSurface = childSurface;
+ mBGColor = bgColor;
+ mCreateTime = SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ if (!immediately) {
+ mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+ info.windowAnimationLeash, info.mainFrame,
+ this::release, mCreateTime, 0 /* roundedCornerRadius */);
+ } else {
+ release();
+ }
+ }
+
+ void release() {
+ if (mChildSurface != null) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ t.remove(mChildSurface).apply();
+ mTransactionPool.release(t);
+ mChildSurface = null;
+ }
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index bb43d7c1a090..72fc8686f648 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -22,6 +22,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
@@ -30,6 +31,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
@@ -55,6 +57,7 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
+ final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
@@ -67,10 +70,15 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
+ "isSolidColorSplashScreen=%b, "
+ "legacySplashScreen=%b, "
+ "activityDrawn=%b, "
+ + "windowless=%b, "
+ "topIsHome=%b",
newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
- isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
+ topIsHome);
+ if (windowlessSurface) {
+ return STARTING_WINDOW_TYPE_WINDOWLESS;
+ }
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
index 74fe8fbbd5e0..04ae2f960102 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_SOLID_COLOR_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
import android.window.StartingWindowInfo;
@@ -24,12 +24,12 @@ import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
/**
* Algorithm for determining the type of a new starting window on Android TV.
- * For now we always show empty splash screens on Android TV.
+ * For now we do not want to show any splash screens on Android TV.
*/
public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
@Override
public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
- // For now we want to always show empty splash screens on TV.
- return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
+ // For now we do not want to show any splash screens on TV.
+ return STARTING_WINDOW_TYPE_NONE;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index ac52235375c4..2e2f569a52b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import android.os.Build;
import android.os.SystemClock;
import android.util.Pair;
+import android.view.SurfaceControl;
import androidx.annotation.VisibleForTesting;
@@ -75,6 +76,7 @@ public class ShellInit {
@VisibleForTesting
public void init() {
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
+ SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
for (int i = 0; i < mInitCallbacks.size(); i++) {
final Pair<String, Runnable> info = mInitCallbacks.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 19133e29de4b..628ce27fe514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -28,6 +28,7 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import com.android.wm.shell.util.CounterRotator;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
@@ -57,7 +58,7 @@ public class CounterRotatorHelper {
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())
+ if (!TransitionUtil.isClosingType(change.getMode())
|| !TransitionInfo.isIndependent(change, info) || parent == null) {
continue;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index a2d7bc43653a..aa1e6ed8bcf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -17,6 +17,8 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -24,23 +26,28 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Optional;
@@ -49,10 +56,12 @@ import java.util.Optional;
* A handler for dealing with transitions involving multiple other handlers. For example: an
* activity in split-screen going into PiP.
*/
-public class DefaultMixedHandler implements Transitions.TransitionHandler {
+public class DefaultMixedHandler implements Transitions.TransitionHandler,
+ RecentsTransitionHandler.RecentsMixedHandler {
private final Transitions mPlayer;
private PipTransitionController mPipHandler;
+ private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private static class MixedTransition {
@@ -61,17 +70,25 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
/** Both the display and split-state (enter/exit) is changing */
static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2;
+ /** Pip was entered while handling an intent with its own remoteTransition. */
+ static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
+
+ /** Recents transition while split-screen active. */
+ static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
/** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
static final int ANIM_TYPE_GOING_HOME = 1;
+ /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+ static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
final int mType;
- int mAnimType = 0;
+ int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
- Transitions.TransitionFinishCallback mFinishCallback = null;
Transitions.TransitionHandler mLeftoversHandler = null;
WindowContainerTransaction mFinishWCT = null;
@@ -85,13 +102,31 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mType = type;
mTransition = transition;
}
+
+ void joinFinishArgs(WindowContainerTransaction wct,
+ WindowContainerTransactionCallback wctCB) {
+ if (wctCB != null) {
+ // Technically can probably support 1, but don't want to encourage CB usage since
+ // it creates instabliity, so just throw.
+ throw new IllegalArgumentException("Can't mix transitions that require finish"
+ + " sync callback");
+ }
+ if (wct != null) {
+ if (mFinishWCT == null) {
+ mFinishWCT = wct;
+ } else {
+ mFinishWCT.merge(wct, true /* transfer */);
+ }
+ }
+ }
}
private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>();
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional) {
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Optional<RecentsTransitionHandler> recentsHandlerOptional) {
mPlayer = player;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
&& splitScreenControllerOptional.isPresent()) {
@@ -103,6 +138,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mSplitHandler != null) {
mSplitHandler.setMixedHandler(this);
}
+ mRecentsHandler = recentsHandlerOptional.orElse(null);
+ if (mRecentsHandler != null) {
+ mRecentsHandler.addMixer(this);
+ }
}, this);
}
}
@@ -125,10 +164,73 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getRemoteTransition() != null
+ && TransitionUtil.isOpeningType(request.getType())
+ && (request.getTriggerTask() == null
+ || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME
+ && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) {
+ // Only select transitions with an intent-provided remote-animation because that will
+ // usually grab priority and often won't handle PiP. If there isn't an intent-provided
+ // remote, then the transition will be dispatched normally and the PipHandler will
+ // pick it up.
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+ mPlayer.dispatchRequest(transition, request, this);
+ if (handler == null) {
+ return null;
+ }
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
+ mixed.mLeftoversHandler = handler.first;
+ mActiveTransitions.add(mixed);
+ return handler.second;
+ } else if (mSplitHandler.isSplitActive()
+ && isOpeningType(request.getType())
+ && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
+ || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)
+ && request.getRemoteTransition() != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+ mPlayer.dispatchRequest(transition, request, this);
+ if (handler == null) {
+ android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected"
+ + ", there should at-least be RemoteHandler.");
+ return null;
+ }
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = handler.first;
+ mActiveTransitions.add(mixed);
+ return handler.second;
+ }
+ return null;
+ }
+
+ @Override
+ public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
+ if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+ return this;
}
return null;
}
+ @Override
+ public void setRecentsTransition(IBinder transition) {
+ if (mSplitHandler.isSplitActive()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
+ } else {
+ throw new IllegalStateException("Accepted a recents transition but don't know how to"
+ + " handle it");
+ }
+ }
+
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
@@ -137,14 +239,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
out.getChanges().add(info.getChanges().get(i));
}
}
- out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ out.addRoot(info.getRoot(i));
+ }
out.setAnimationOptions(info.getAnimationOptions());
return out;
}
private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
return change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME;
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
}
private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
@@ -169,6 +273,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -176,6 +286,64 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
}
}
+ private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ info.getChanges().remove(i);
+ }
+ }
+ if (pipChange == null) {
+ if (mixed.mLeftoversHandler != null) {
+ if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
+ info, startTransaction, finishTransaction, (wct, wctCB) -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct, wctCB);
+ })) {
+ return true;
+ }
+ }
+ mActiveTransitions.remove(mixed);
+ return false;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+ + " animation because remote-animation likely doesn't support it");
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct, wctCB);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ };
+ // Split the transition into 2 parts: the pip part and the rest.
+ mixed.mInFlightSubAnimations = 2;
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+
+ mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+ // Dispatch the rest of the transition normally.
+ if (mixed.mLeftoversHandler != null
+ && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB, this);
+ return true;
+ }
+
private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -205,18 +373,19 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
}
if (pipChange == null) {
// um, something probably went wrong.
+ mActiveTransitions.remove(mixed);
return false;
}
final boolean isGoingHome = homeIsOpening;
- mixed.mFinishCallback = finishCallback;
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct, wctCB);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
if (isGoingHome) {
mSplitHandler.onTransitionAnimationComplete();
}
- mixed.mFinishCallback.onTransitionFinished(wct, wctCB);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
};
if (isGoingHome) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
@@ -308,7 +477,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
unlinkMissingParents(everythingElse);
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
- mixed.mFinishCallback = finishCallback;
mActiveTransitions.add(mixed);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
+ "and split change.");
@@ -317,20 +485,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
- if (wctCB != null) {
- throw new IllegalArgumentException("Can't mix transitions that require finish"
- + " sync callback");
- }
- if (wct != null) {
- if (mixed.mFinishWCT == null) {
- mixed.mFinishWCT = wct;
- } else {
- mixed.mFinishWCT.merge(wct, true /* transfer */);
- }
- }
+ mixed.joinFinishArgs(wct, wctCB);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
};
// Dispatch the display change. This will most-likely be taken by the default handler.
@@ -342,23 +500,52 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
// Note: at this point, startT has probably already been applied, so we are basically
// giving splitHandler an empty startT. This is currently OK because display-change will
// grab a screenshot and paste it on top anyways.
- mSplitHandler.startPendingAnimation(
- transition, everythingElse, startT, finishT, finishCB);
+ mSplitHandler.startPendingAnimation(transition, everythingElse, startT, finishT, finishCB);
return true;
}
+ private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations = 0;
+ mActiveTransitions.remove(mixed);
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct, wctCB);
+ };
+ mixed.mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+ final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mActiveTransitions.remove(mixed);
+ }
+ return handled;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
- if (mActiveTransitions.get(i) != mergeTarget) continue;
+ if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
return;
}
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ // queue since no actual animation.
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
boolean ended = mSplitHandler.end();
// If split couldn't end (because it is remote), then don't end everything else
@@ -372,8 +559,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
} else {
mPipHandler.end();
}
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- // queue
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ mPipHandler.end();
+ if (mixed.mLeftoversHandler != null) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ }
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -393,6 +592,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 928e71f8d3a6..63c7969291a0 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
@@ -41,6 +41,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -63,7 +64,6 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackg
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -112,6 +112,7 @@ 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.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -300,6 +301,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ // check if no-animation and skip animation if so.
+ if (Transitions.isAllNoAnimation(info)) {
+ startTransaction.apply();
+ finishTransaction.apply();
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ return true;
+ }
+
if (mAnimations.containsKey(transition)) {
throw new IllegalStateException("Got a duplicate startAnimation call for "
+ transition);
@@ -374,9 +383,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
// No default animation for this, so just update bounds/position.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - info.getRootOffset().x,
- change.getEndAbsBounds().top - info.getRootOffset().y);
+ change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -395,6 +405,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
+ // The back gesture has animated this change before transition happen, so here we don't
+ // play the animation again.
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ continue;
+ }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
@@ -431,9 +446,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
- boolean delayedEdgeExtension = false;
if (!isTask && a.hasExtension()) {
- if (!Transitions.isOpeningType(change.getMode())) {
+ if (!TransitionUtil.isOpeningType(change.getMode())) {
// Can screenshot now (before startTransaction is applied)
edgeExtendWindow(change, a, startTransaction, finishTransaction);
} else {
@@ -441,28 +455,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// may not be visible or ready yet.
postStartTransactionCallbacks
.add(t -> edgeExtendWindow(change, a, t, finishTransaction));
- delayedEdgeExtension = true;
}
}
- final Rect clipRect = Transitions.isClosingType(change.getMode())
+ final Rect clipRect = TransitionUtil.isClosingType(change.getMode())
? new Rect(mRotator.getEndBoundsInStartRotation(change))
: new Rect(change.getEndAbsBounds());
clipRect.offsetTo(0, 0);
- if (delayedEdgeExtension) {
- // If the edge extension needs to happen after the startTransition has been
- // 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,
- change.getEndRelOffset(), cornerRadius, clipRect));
- } else {
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor,
- change.getEndRelOffset(), cornerRadius, clipRect);
- }
+ buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius,
+ clipRect);
if (info.getAnimationOptions() != null) {
attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
@@ -472,23 +475,31 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
- startTransaction, finishTransaction);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+ startTransaction, finishTransaction);
+ }
}
- // 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);
+ if (postStartTransactionCallbacks.size() > 0) {
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ startTransaction.apply(true /* sync */);
+ // startTransaction is empty now, so fill it with the edge-extension setup
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ postStartTransactionCallback.accept(startTransaction);
+ }
}
+ startTransaction.apply();
+
+ // now start animations. they are started on another thread, so we have to post them
+ // *after* applying the startTransaction
+ mAnimExecutor.execute(() -> {
+ for (int i = 0; i < animations.size(); ++i) {
+ animations.get(i).start();
+ }
+ });
mRotator.cleanUp(finishTransaction);
TransitionMetrics.getInstance().reportAnimationStart(transition);
@@ -512,8 +523,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+ mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+ animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
@@ -525,8 +538,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
animations.removeAll(animGroupStore);
onAnimFinish.run();
};
- anim.startAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
- mMainExecutor, mAnimExecutor);
+ anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
+ mMainExecutor);
for (int i = animGroup.size() - 1; i >= 0; i--) {
final Animator animator = animGroup.get(i);
animGroupStore.add(animator);
@@ -555,13 +568,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int flags = info.getFlags();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
- final boolean isOpeningType = Transitions.isOpeningType(type);
- final boolean enter = Transitions.isOpeningType(changeMode);
+ final boolean isOpeningType = TransitionUtil.isOpeningType(type);
+ final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
- final Rect endBounds = Transitions.isClosingType(changeMode)
+ final Rect endBounds = TransitionUtil.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
@@ -583,7 +595,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} else if (type == TRANSIT_RELAUNCH) {
a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
} else if (overrideType == ANIM_CUSTOM
- && (canCustomContainer || options.getOverrideTaskTransition())) {
+ && (!isTask || options.getOverrideTaskTransition())) {
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
? options.getEnterResId() : options.getExitResId());
} else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
@@ -619,11 +631,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return a;
}
- static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+ /** Builds an animator for the surface and adds it to the `animations` list. */
+ static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Animation anim, @NonNull SurfaceControl leash,
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
- @Nullable Point position, float cornerRadius, @Nullable Rect clipRect) {
+ @NonNull ShellExecutor mainExecutor, @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();
@@ -677,14 +690,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
});
animations.add(va);
- animExecutor.execute(va::start);
}
private void attachThumbnail(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change,
TransitionInfo.AnimationOptions options, float cornerRadius) {
- final boolean isOpen = Transitions.isOpeningType(change.getMode());
- final boolean isClose = Transitions.isClosingType(change.getMode());
+ final boolean isOpen = TransitionUtil.isOpeningType(change.getMode());
+ final boolean isClose = TransitionUtil.isClosingType(change.getMode());
if (isOpen) {
if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
@@ -731,9 +743,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
};
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
- cornerRadius, change.getEndAbsBounds());
+ buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@@ -756,9 +767,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
};
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
- cornerRadius, change.getEndAbsBounds());
+ buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private static int getWallpaperTransitType(TransitionInfo info) {
@@ -768,16 +778,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
- if (Transitions.isOpeningType(change.getMode())) {
+ if (TransitionUtil.isOpeningType(change.getMode())) {
hasOpenWallpaper = true;
- } else if (Transitions.isClosingType(change.getMode())) {
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
hasCloseWallpaper = true;
}
}
}
if (hasOpenWallpaper && hasCloseWallpaper) {
- return Transitions.isOpeningType(info.getType())
+ return TransitionUtil.isOpeningType(info.getType())
? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
} else if (hasOpenWallpaper) {
return WALLPAPER_TRANSITION_OPEN;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index bdcdb63d2cd6..cc4d268a0000 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -34,4 +34,9 @@ interface IShellTransitions {
* Unregisters a remote transition handler.
*/
oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2;
+
+ /**
+ * Retrieves the apply-token used by transactions in Shell
+ */
+ IBinder getShellApplyToken() = 3;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index b4e05848882c..3c4e8898f215 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -39,6 +39,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -93,6 +94,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
+ // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
+ // operations of the start transaction may be ignored.
+ return false;
+ }
RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
if (pendingRemote == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
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 b647f43da522..e643170273de 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
@@ -16,14 +16,12 @@
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;
+import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation;
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
@@ -37,8 +35,6 @@ import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.media.Image;
-import android.media.ImageReader;
import android.util.Slog;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -46,15 +42,15 @@ import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* This class handles the rotation animation when the device is rotated.
@@ -144,14 +140,14 @@ class ScreenRotationAnimation {
t.reparent(mScreenshotLayer, mAnimLeash);
mStartLuma = change.getSnapshotLuma();
} else {
- SurfaceControl.LayerCaptureArgs args =
- new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
+ ScreenCapture.LayerCaptureArgs args =
+ new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl)
.setCaptureSecureLayers(true)
.setAllowProtected(true)
.setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
.build();
- SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
- SurfaceControl.captureLayers(args);
+ ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+ ScreenCapture.captureLayers(args);
if (screenshotBuffer == null) {
Slog.w(TAG, "Unable to take screenshot of display");
return;
@@ -172,8 +168,9 @@ class ScreenRotationAnimation {
t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
- mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
+ mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
}
+ hardwareBuffer.close();
}
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
@@ -247,11 +244,11 @@ class ScreenRotationAnimation {
}
/**
- * Returns true if animating.
+ * Returns true if any animations were added to `animations`.
*/
- public boolean startAnimation(@NonNull ArrayList<Animator> animations,
+ boolean buildAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, float animationScale,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor) {
if (mScreenshotLayer == null) {
// Can't do animation.
return false;
@@ -314,13 +311,11 @@ class ScreenRotationAnimation {
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
- startScreenshotAlphaAnimation(animations, finishCallback, mainExecutor,
- animExecutor);
- startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+ buildScreenshotAlphaAnimation(animations, finishCallback, mainExecutor);
+ startDisplayRotation(animations, finishCallback, mainExecutor);
} else {
- startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
- startScreenshotRotationAnimation(animations, finishCallback, mainExecutor,
- animExecutor);
+ startDisplayRotation(animations, finishCallback, mainExecutor);
+ startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
//startColorAnimation(mTransaction, animationScale);
}
@@ -328,27 +323,24 @@ class ScreenRotationAnimation {
}
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
- startSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */,
- 0 /* cornerRadius */, null /* clipRect */);
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+ mTransactionPool, mainExecutor, 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 */,
- 0 /* cornerRadius */, null /* clipRect */);
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, 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 buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
+ null /* clipRect */);
}
private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
@@ -403,93 +395,6 @@ class ScreenRotationAnimation {
mTransactionPool.release(t);
}
- /**
- * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
- * luminance at the borders of the bitmap
- * @return the average luminance of all the pixels at the borders of the bitmap
- */
- private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
- // Cannot read content from buffer with protected usage.
- if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
- || hasProtectedContent(hardwareBuffer)) {
- return 0;
- }
-
- ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
- hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
- ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
- Image image = ir.acquireLatestImage();
- if (image == null || image.getPlanes().length == 0) {
- return 0;
- }
-
- Image.Plane plane = image.getPlanes()[0];
- ByteBuffer buffer = plane.getBuffer();
- int width = image.getWidth();
- int height = image.getHeight();
- int pixelStride = plane.getPixelStride();
- int rowStride = plane.getRowStride();
- float[] borderLumas = new float[2 * width + 2 * height];
-
- // Grab the top and bottom borders
- int l = 0;
- for (int x = 0; x < width; x++) {
- borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
- }
-
- // Grab the left and right borders
- for (int y = 0; y < height; y++) {
- borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
- borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
- }
-
- // Cleanup
- ir.close();
-
- // Oh, is this too simple and inefficient for you?
- // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
- Arrays.sort(borderLumas);
- return borderLumas[borderLumas.length / 2];
- }
-
- /**
- * @return whether the hardwareBuffer passed in is marked as protected.
- */
- private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
- return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
- }
-
- private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
- int pixelStride, int rowStride) {
- int offset = y * rowStride + x * pixelStride;
- int pixel = 0;
- pixel |= (buffer.get(offset) & 0xff) << 16; // R
- pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
- pixel |= (buffer.get(offset + 2) & 0xff); // B
- pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
- return Color.valueOf(pixel).luminance();
- }
-
- /**
- * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
- * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
- */
- private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) {
- if (surfaceControl == null) {
- return 0;
- }
-
- Rect crop = new Rect(0, 0, bounds.width(), bounds.height());
- SurfaceControl.ScreenshotHardwareBuffer buffer =
- SurfaceControl.captureLayers(surfaceControl, crop, 1);
- if (buffer == null) {
- return 0;
- }
-
- return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
- }
-
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
new file mode 100644
index 000000000000..0386ec38a3ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import java.util.ArrayList;
+
+/**
+ * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
+ * as sentinels for fast-forwarding through animations when the screen is off.
+ *
+ * There should only be one SleepHandler and it is used explicitly by {@link Transitions} so we
+ * don't register it like a normal handler.
+ */
+class SleepHandler implements Transitions.TransitionHandler {
+ final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null, null);
+ mSleepTransitions.remove(transition);
+ return true;
+ }
+
+ @Override
+ @Nullable
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ mSleepTransitions.add(transition);
+ return new WindowContainerTransaction();
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
+ mSleepTransitions.remove(transition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index ab792ee122c7..bcc37baa5b00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -43,36 +43,22 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Shader;
-import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.view.animation.Transformation;
+import android.window.ScreenCapture;
import android.window.TransitionInfo;
import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
- /**
- * Restrict ability of activities overriding transition animation in a way such that
- * an activity can do it only when the transition happens within a same task.
- *
- * @see android.app.Activity#overridePendingTransition(int, int)
- */
- private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
- "persist.wm.disable_custom_task_animation";
-
- /**
- * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
- */
- static final boolean sDisableCustomTaskAnimationProperty =
- SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
-
/** Loads the animation that is defined through attribute id for the given transition. */
@Nullable
public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
@@ -81,11 +67,10 @@ public class TransitionAnimationHelper {
final int type = info.getType();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
- final boolean enter = Transitions.isOpeningType(changeMode);
+ final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final boolean isDream =
isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
int animAttr = 0;
@@ -157,10 +142,17 @@ public class TransitionAnimationHelper {
Animation a = null;
if (animAttr != 0) {
- if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
- a = transitionAnimation
- .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
- animAttr, translucent);
+ if (overrideType == ANIM_FROM_STYLE && !isTask) {
+ final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition =
+ getCustomActivityTransition(animAttr, options);
+ if (customTransition != null) {
+ a = loadCustomActivityTransition(
+ customTransition, options, enter, transitionAnimation);
+ } else {
+ a = transitionAnimation
+ .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
+ animAttr, translucent);
+ }
} else {
a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
@@ -173,6 +165,37 @@ public class TransitionAnimationHelper {
return a;
}
+ static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition(
+ int animAttr, TransitionInfo.AnimationOptions options) {
+ boolean isOpen = false;
+ switch (animAttr) {
+ case R.styleable.WindowAnimation_activityOpenEnterAnimation:
+ case R.styleable.WindowAnimation_activityOpenExitAnimation:
+ isOpen = true;
+ break;
+ case R.styleable.WindowAnimation_activityCloseEnterAnimation:
+ case R.styleable.WindowAnimation_activityCloseExitAnimation:
+ break;
+ default:
+ return null;
+ }
+
+ return options.getCustomActivityTransition(isOpen);
+ }
+
+ static Animation loadCustomActivityTransition(
+ @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
+ TransitionInfo.AnimationOptions options, boolean enter,
+ TransitionAnimation transitionAnimation) {
+ final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
+ enter ? transitionAnim.getCustomEnterResId()
+ : transitionAnim.getCustomExitResId());
+ if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
+ a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
+ }
+ return a;
+ }
+
/**
* Gets the background {@link ColorInt} for the given transition animation if it is set.
*
@@ -314,8 +337,8 @@ public class TransitionAnimationHelper {
.setBufferSize(extensionRect.width(), extensionRect.height())
.build();
- final SurfaceControl.LayerCaptureArgs captureArgs =
- new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ final ScreenCapture.LayerCaptureArgs captureArgs =
+ new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
.setSourceCrop(edgeBounds)
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
@@ -323,8 +346,8 @@ public class TransitionAnimationHelper {
.setAllowProtected(true)
.setCaptureSecureLayers(true)
.build();
- final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
- SurfaceControl.captureLayers(captureArgs);
+ final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
+ ScreenCapture.captureLayers(captureArgs);
if (edgeBuffer == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
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 39fe4559c88f..039bde95815e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -19,17 +19,21 @@ package com.android.wm.shell.transition;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +48,7 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITransitionPlayer;
@@ -70,17 +75,35 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Arrays;
-/** Plays transition animations */
+/**
+ * Plays transition animations. Within this player, each transition has a lifecycle.
+ * 1. When a transition is directly started or requested, it is added to "pending" state.
+ * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
+ * 3. When a transition starts animating, it is moved to the "active" state.
+ *
+ * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
+ * --merge--> MERGED --^
+ *
+ * At the moment, only one transition can be animating at a time. While a transition is animating,
+ * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
+ * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
+ * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
+ * "merged" and then the next "ready" transition can attempt to merge.
+ *
+ * Once the "active" transition animation is finished, it will be removed from the "active" list
+ * and then the next "ready" transition can play.
+ */
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
@@ -120,6 +143,7 @@ public class Transitions implements RemoteCallable<Transitions> {
private final DisplayController mDisplayController;
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private final SleepHandler mSleepHandler = new SleepHandler();
private boolean mIsRegistered = false;
@@ -133,17 +157,33 @@ public class Transitions implements RemoteCallable<Transitions> {
private float mTransitionAnimationScaleSetting = 1.0f;
+ /**
+ * How much time we allow for an animation to finish itself on sleep. If it takes longer, we
+ * will force-finish it (on this end) which may leave it in a bad state but won't hang the
+ * device. This needs to be pretty small because it is an allowance for each queued animation,
+ * however it can't be too small since there is some potential IPC involved.
+ */
+ private static final int SLEEP_ALLOWANCE_MS = 120;
+
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
- boolean mMerged;
boolean mAborted;
TransitionInfo mInfo;
SurfaceControl.Transaction mStartT;
SurfaceControl.Transaction mFinishT;
+
+ /** Ordered list of transitions which have been merged into this one. */
+ private ArrayList<ActiveTransition> mMerged;
}
- /** Keeps track of currently playing transitions in the order of receipt. */
+ /** Keeps track of transitions which have been started, but aren't ready yet. */
+ private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
+
+ /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+ private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+ /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull Context context,
@@ -174,6 +214,9 @@ public class Transitions implements RemoteCallable<Transitions> {
}
private void onInit() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mOrganizer.shareTransactionQueue();
+ }
mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
this::createExternalInterface, this);
@@ -305,25 +348,14 @@ public class Transitions implements RemoteCallable<Transitions> {
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
- if (mActiveTransitions.isEmpty()) {
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
+ && mReadyTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
}
}
- /** @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
- || type == TRANSIT_TO_FRONT
- || type == TRANSIT_KEYGUARD_GOING_AWAY;
- }
-
- /** @return true if the transition was triggered by closing something vs opening something */
- public static boolean isClosingType(@WindowManager.TransitionType int type) {
- return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
- }
-
/**
* Sets up visibility/alpha/transforms to resemble the starting state of an animation.
*/
@@ -384,8 +416,8 @@ public class Transitions implements RemoteCallable<Transitions> {
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
- if (info.getRootLeash().isValid()) {
- t.show(info.getRootLeash());
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
@@ -403,10 +435,12 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean hasParent = change.getParent() != null;
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (!hasParent) {
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ t.setPosition(leash,
+ change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
@@ -436,38 +470,85 @@ public class Transitions implements RemoteCallable<Transitions> {
}
}
- private int findActiveTransition(IBinder token) {
- for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
- if (mActiveTransitions.get(i).mToken == token) return i;
+ private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken == token) return i;
}
return -1;
}
+ /**
+ * Look through a transition and see if all non-closing changes are no-animation. If so, no
+ * animation should play.
+ */
+ static boolean isAllNoAnimation(TransitionInfo info) {
+ if (isClosingType(info.getType())) {
+ // no-animation is only relevant for launching (open) activities.
+ return false;
+ }
+ boolean hasNoAnimation = false;
+ final int changeSize = info.getChanges().size();
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isClosingType(change.getMode())) {
+ // ignore closing apps since they are a side-effect of the transition and don't
+ // animate.
+ continue;
+ }
+ if (change.hasFlags(FLAG_NO_ANIMATION)) {
+ hasNoAnimation = true;
+ } else {
+ // at-least one relevant participant *is* animated, so we need to animate.
+ return false;
+ }
+ }
+ return hasNoAnimation;
+ }
+
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
- final int activeIdx = findActiveTransition(transitionToken);
+ final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
- throw new IllegalStateException("Got transitionReady for non-active transition "
+ throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
- + Arrays.toString(mActiveTransitions.stream().map(
+ + Arrays.toString(mPendingTransitions.stream().map(
+ activeTransition -> activeTransition.mToken).toArray()));
+ }
+ if (activeIdx > 0) {
+ Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
+ + " order: " + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
+ // Move from pending to ready
+ final ActiveTransition active = mPendingTransitions.remove(activeIdx);
+ mReadyTransitions.add(active);
+ active.mInfo = info;
+ active.mStartT = t;
+ active.mFinishT = finishT;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
}
- if (!info.getRootLeash().isValid()) {
- // Invalid root-leash implies that the transition is empty/no-op, so just do
+ if (info.getType() == TRANSIT_SLEEP) {
+ if (activeIdx > 0) {
+ // Sleep starts a process of forcing all prior transitions to finish immediately
+ finishForSleep(null /* forceFinish */);
+ return;
+ }
+ }
+
+ // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
+ // screen-off, so there might no visibility change involved.
+ if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
+ // No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
transitionToken, info);
- t.apply();
- finishT.apply();
- onAbort(transitionToken);
+ onAbort(active);
return;
}
@@ -493,41 +574,90 @@ public class Transitions implements RemoteCallable<Transitions> {
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
- t.apply();
- finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
- onAbort(transitionToken);
- releaseSurfaces(info);
+ onAbort(active);
return;
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
- if (activeIdx > 0) {
- // This is now playing at the same time as an existing animation, so try merging it.
- attemptMergeTransition(mActiveTransitions.get(0), active);
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
return;
}
- // The normal case, just play it.
- playTransition(active);
+ processReadyQueue();
}
- /**
- * Attempt to merge by delegating the transition start to the handler of the currently
- * playing transition.
- */
- void attemptMergeTransition(@NonNull ActiveTransition playing,
- @NonNull ActiveTransition merging) {
+ void processReadyQueue() {
+ if (mReadyTransitions.isEmpty()) {
+ // Check if idle.
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ + "animations finished");
+ // Run all runnables from the run-when-idle queue.
+ for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+ mRunWhenIdleQueue.get(i).run();
+ }
+ mRunWhenIdleQueue.clear();
+ }
+ return;
+ }
+ final ActiveTransition ready = mReadyTransitions.get(0);
+ if (mActiveTransitions.isEmpty()) {
+ // The normal case, just play it (currently we only support 1 active transition).
+ mReadyTransitions.remove(0);
+ mActiveTransitions.add(ready);
+ if (ready.mAborted) {
+ // finish now since there's nothing to animate. Calls back into processReadyQueue
+ onFinish(ready, null, null);
+ return;
+ }
+ playTransition(ready);
+ // Attempt to merge any more queued-up transitions.
+ processReadyQueue();
+ return;
+ }
+ // An existing animation is playing, so see if we can merge.
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ if (ready.mAborted) {
+ // record as merged since it is no-op. Calls back into processReadyQueue
+ onMerged(playing, ready);
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " another transition %s is still animating. Notify the animating transition"
- + " in case they can be merged", merging.mToken, playing.mToken);
- playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
- playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
+ + " in case they can be merged", ready.mToken, playing.mToken);
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mToken, (wct, cb) -> onMerged(playing, ready));
+ }
+
+ private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
+ merged.mToken);
+ int readyIdx = 0;
+ if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+ Log.e(TAG, "Merged transition out-of-order?");
+ readyIdx = mReadyTransitions.indexOf(merged);
+ if (readyIdx < 0) {
+ Log.e(TAG, "Merged a transition that is no-longer queued?");
+ return;
+ }
+ }
+ mReadyTransitions.remove(readyIdx);
+ if (playing.mMerged == null) {
+ playing.mMerged = new ArrayList<>();
+ }
+ playing.mMerged.add(merged);
+ // if it was aborted, then onConsumed has already been reported.
+ if (merged.mHandler != null && !merged.mAborted) {
+ merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
+ }
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
+ }
+ // See if we should merge another transition.
+ processReadyQueue();
}
private void playTransition(@NonNull ActiveTransition active) {
@@ -542,7 +672,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
- active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
@@ -550,7 +680,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
- active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
/**
@@ -580,27 +710,41 @@ public class Transitions implements RemoteCallable<Transitions> {
* Gives every handler (in order) a chance to handle request until one consumes the transition.
* @return the WindowContainerTransaction given by the handler which consumed the transition.
*/
- public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+ public Pair<TransitionHandler, WindowContainerTransaction> dispatchRequest(
+ @NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @Nullable TransitionHandler skip) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
if (mHandlers.get(i) == skip) continue;
WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
if (wct != null) {
- return wct;
+ return new Pair<>(mHandlers.get(i), wct);
}
}
return null;
}
- /** Special version of finish just for dealing with no-op/invalid transitions. */
- private void onAbort(IBinder transition) {
- onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
- }
+ /** Aborts a transition. This will still queue it up to maintain order. */
+ private void onAbort(ActiveTransition transition) {
+ // apply immediately since they may be "parallel" operations: We currently we use abort for
+ // thing which are independent to other transitions (like starting-window transfer).
+ transition.mStartT.apply();
+ transition.mFinishT.apply();
+ transition.mAborted = true;
- private void onFinish(IBinder transition,
- @Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB) {
- onFinish(transition, wct, wctCB, false /* abort */);
+ if (transition.mHandler != null) {
+ // Notifies to clean-up the aborted transition.
+ transition.mHandler.onTransitionConsumed(
+ transition.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+
+ releaseSurfaces(transition.mInfo);
+
+ // This still went into the queue (to maintain the correct finish ordering).
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
+ return;
+ }
+ processReadyQueue();
}
/**
@@ -612,180 +756,136 @@ public class Transitions implements RemoteCallable<Transitions> {
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder transition,
+ private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB,
- boolean abort) {
- int activeIdx = findActiveTransition(transition);
+ @Nullable WindowContainerTransactionCallback wctCB) {
+ int activeIdx = mActiveTransitions.indexOf(active);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
- + " a handler didn't properly deal with a merge.", new RuntimeException());
- return;
- } else if (activeIdx > 0) {
- // This transition was merged.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
- + " %s", abort, transition);
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mMerged = true;
- active.mAborted = abort;
- if (active.mHandler != null) {
- active.mHandler.onTransitionConsumed(
- active.mToken, abort, abort ? null : active.mFinishT);
- }
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionMerged(
- active.mToken, mActiveTransitions.get(0).mToken);
- }
+ + " a handler didn't properly deal with a merge. " + active.mToken,
+ new RuntimeException());
return;
+ } else if (activeIdx != 0) {
+ // Relevant right now since we only allow 1 active transition at a time.
+ Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mAborted = abort;
- if (active.mAborted && active.mHandler != null) {
- // Notifies to clean-up the aborted transition.
- active.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
- }
+ mActiveTransitions.remove(activeIdx);
+
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
if (active.mStartT != null) {
- // Applied by now, so close immediately. Do not set to null yet, though, since nullness
- // is used later to disambiguate malformed transitions.
- active.mStartT.close();
+ // Applied by now, so clear immediately to remove any references. Do not set to null
+ // yet, though, since nullness is used later to disambiguate malformed transitions.
+ active.mStartT.clear();
}
- // Merge all relevant transactions together
+ // Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
- for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
- final ActiveTransition toMerge = mActiveTransitions.get(iA);
- if (!toMerge.mMerged) break;
- // aborted transitions have no start/finish transactions
- if (mActiveTransitions.get(iA).mStartT == null) break;
- if (fullFinish == null) {
- fullFinish = new SurfaceControl.Transaction();
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ final ActiveTransition toMerge = active.mMerged.get(iM);
+ // Include start. It will be a no-op if it was already applied. Otherwise, we need
+ // it to maintain consistent state.
+ if (toMerge.mStartT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mStartT;
+ } else {
+ fullFinish.merge(toMerge.mStartT);
+ }
+ }
+ if (toMerge.mFinishT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mFinishT;
+ } else {
+ fullFinish.merge(toMerge.mFinishT);
+ }
+ }
}
- // Include start. It will be a no-op if it was already applied. Otherwise, we need it
- // to maintain consistent state.
- fullFinish.merge(mActiveTransitions.get(iA).mStartT);
- fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
}
if (fullFinish != null) {
fullFinish.apply();
}
- // Now perform all the finishes.
+ // Now perform all the finish callbacks (starting with the playing one and then all the
+ // transitions merged into it).
releaseSurfaces(active.mInfo);
- mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(transition, wct, wctCB);
- while (activeIdx < mActiveTransitions.size()) {
- if (!mActiveTransitions.get(activeIdx).mMerged) break;
- ActiveTransition merged = mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
- releaseSurfaces(merged.mInfo);
- }
- // sift through aborted transitions
- while (mActiveTransitions.size() > activeIdx
- && mActiveTransitions.get(activeIdx).mAborted) {
- ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
- // Notifies to clean-up the aborted transition.
- if (aborted.mHandler != null) {
- aborted.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
- }
- mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(aborted.mToken, true);
+ mOrganizer.finishTransition(active.mToken, wct, wctCB);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ ActiveTransition merged = active.mMerged.get(iM);
+ mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
- releaseSurfaces(aborted.mInfo);
+ active.mMerged.clear();
}
- if (mActiveTransitions.size() <= activeIdx) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
- + "finished");
- // Run all runnables from the run-when-idle queue.
- for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
- mRunWhenIdleQueue.get(i).run();
- }
- mRunWhenIdleQueue.clear();
- return;
- }
- // Start animating the next active transition
- final ActiveTransition next = mActiveTransitions.get(activeIdx);
- if (next.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
- + " finished, but it isn't ready yet.");
- return;
+
+ // Now that this is done, check the ready queue for more work.
+ processReadyQueue();
+ }
+
+ private boolean isTransitionKnown(IBinder token) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ if (mPendingTransitions.get(i).mToken == token) return true;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
- + " finished, so start the next one.");
- playTransition(next);
- // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
- // finished immediately)
- activeIdx = findActiveTransition(next.mToken);
- if (activeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in the
- // re-entered call.
- return;
+ for (int i = 0; i < mReadyTransitions.size(); ++i) {
+ if (mReadyTransitions.get(i).mToken == token) return true;
}
-
- // This logic is also convoluted because 'next' may finish immediately in response to any of
- // the merge requests (eg. if it decided to "cancel" itself).
- int mergeIdx = activeIdx + 1;
- while (mergeIdx < mActiveTransitions.size()) {
- ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
- if (mergeCandidate.mAborted) {
- // transition was aborted, so we can skip for now (still leave it in the list
- // so that it gets cleaned-up in the right order).
- ++mergeIdx;
- continue;
- }
- if (mergeCandidate.mMerged) {
- throw new IllegalStateException("Can't merge a transition after not-merging"
- + " a preceding one.");
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ final ActiveTransition active = mActiveTransitions.get(i);
+ if (active.mToken == token) return true;
+ if (active.mMerged == null) continue;
+ for (int m = 0; m < active.mMerged.size(); ++m) {
+ if (active.mMerged.get(m).mToken == token) return true;
}
- attemptMergeTransition(next, mergeCandidate);
- mergeIdx = findActiveTransition(mergeCandidate.mToken);
- if (mergeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in
- // the re-entered call.
- return;
- }
- ++mergeIdx;
}
+ return false;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
- if (findActiveTransition(transitionToken) >= 0) {
+ if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
WindowContainerTransaction wct = null;
- for (int i = mHandlers.size() - 1; i >= 0; --i) {
- wct = mHandlers.get(i).handleRequest(transitionToken, request);
- if (wct != null) {
- active.mHandler = mHandlers.get(i);
- break;
+
+ // If we have sleep, we use a special handler and we try to finish everything ASAP.
+ if (request.getType() == TRANSIT_SLEEP) {
+ mSleepHandler.handleRequest(transitionToken, request);
+ active.mHandler = mSleepHandler;
+ } else {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ wct = mHandlers.get(i).handleRequest(transitionToken, request);
+ if (wct != null) {
+ active.mHandler = mHandlers.get(i);
+ 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();
+ 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().dispatchOnDisplayChange(wct,
+ change.getDisplayId(), change.getStartRotation(),
+ change.getEndRotation(), null /* newDisplayAreaInfo */);
}
- mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
- change.getDisplayId(), change.getStartRotation(), change.getEndRotation(),
- null /* newDisplayAreaInfo */);
}
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
- mActiveTransitions.add(active);
+ // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
+ // is already collecting that transition on core-side, so it will be the next one to
+ // become ready. There may already be pending transitions added as part of direct
+ // `startNewTransition` but if we have a request now, it means WM created the request
+ // transition before it acknowledged any of the pending `startNew` transitions. So, insert
+ // it at the front.
+ mPendingTransitions.add(0, active);
}
/** Start a new transition directly. */
@@ -794,11 +894,71 @@ public class Transitions implements RemoteCallable<Transitions> {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
- mActiveTransitions.add(active);
+ mPendingTransitions.add(active);
return active.mToken;
}
/**
+ * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this
+ * as both a way to reduce unnecessary work (animations not visible while screen off) and as a
+ * failsafe to unblock "stuck" animations (in particular remote animations).
+ *
+ * This works by "merging" the sleep transition into the currently-playing transition (even if
+ * its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
+ * within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
+ * send an abort/consumed message).
+ *
+ * This is then repeated until there are no more pending sleep transitions.
+ *
+ * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
+ * signal to -- so it will be force-finished if it's still running.
+ */
+ private void finishForSleep(@Nullable ActiveTransition forceFinish) {
+ if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
+ || mSleepHandler.mSleepTransitions.isEmpty()) {
+ // Done finishing things.
+ // Prevent any weird leaks... shouldn't happen though.
+ mSleepHandler.mSleepTransitions.clear();
+ return;
+ }
+ if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
+ Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
+ + forceFinish.mToken);
+ forceFinish.mAborted = true;
+ // Last notify of it being consumed. Note: mHandler should never be null,
+ // but check just to be safe.
+ if (forceFinish.mHandler != null) {
+ forceFinish.mHandler.onTransitionConsumed(
+ forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+ onFinish(forceFinish, null, null);
+ }
+ final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
+ while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
+ if (sleepIdx >= 0) {
+ // Try to signal that we are sleeping by attempting to merge the sleep transition
+ // into the playing one.
+ final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
+ playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
+ playing.mToken, (wct, cb) -> {});
+ } else {
+ Log.e(TAG, "Couldn't find sleep transition in ready list: "
+ + mSleepHandler.mSleepTransitions.get(0));
+ }
+ // it's possible to complete immediately. If that happens, just repeat the signal
+ // loop until we either finish everything or start playing an animation that isn't
+ // finishing immediately.
+ if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
+ // Give it a (very) short amount of time to process it before forcing.
+ mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
+ break;
+ }
+ }
+ }
+
+ /**
* Interface for a callback that must be called after a TransitionHandler finishes playing an
* animation.
*/
@@ -1016,6 +1176,11 @@ public class Transitions implements RemoteCallable<Transitions> {
transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
+
+ @Override
+ public IBinder getShellApplyToken() {
+ return SurfaceControl.Transaction.getDefaultApplyToken();
+ }
}
private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index e0f3fcd932c2..f81fc6fbea49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -35,6 +35,7 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -66,13 +67,12 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final int mExpandedTaskBarHeight;
private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
private final Context mContext;
private final ShellController mShellController;
- private InsetsSource mTaskbarInsetsSource;
+ private InsetsSource mExpandedTaskbarInsetsSource;
private float mWindowCornerRadiusPx;
public FullscreenUnfoldTaskAnimator(Context context,
@@ -82,8 +82,6 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
mDisplayInsetsController = displayInsetsController;
mBackgroundController = backgroundController;
mShellController = shellController;
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
@@ -101,21 +99,32 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(mTaskbarInsetsSource, context.mTaskInfo);
+ context.update(mExpandedTaskbarInsetsSource, context.mTaskInfo);
}
}
+ private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ return source;
+ }
+ }
+ return null;
+ }
+
public boolean hasActiveTasks() {
return mAnimationContextByTaskId.size() > 0;
}
@Override
public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
- taskInfo);
+ AnimationContext animationContext = new AnimationContext(
+ leash, mExpandedTaskbarInsetsSource, taskInfo);
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
}
@@ -123,7 +132,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
public void onTaskChanged(TaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
- animationContext.update(mTaskbarInsetsSource, taskInfo);
+ animationContext.update(mExpandedTaskbarInsetsSource, taskInfo);
}
}
@@ -222,11 +231,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
if (taskBarInsetsSource != null) {
- // Only insets the cropping window with task bar when it's expanded
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(taskBarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
+ mStartCropRect.inset(taskBarInsetsSource.calculateVisibleInsets(mStartCropRect));
}
mEndCropRect.set(mStartCropRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index addd0a6012c4..2f0c96487d68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -37,6 +37,7 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -76,7 +77,6 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
private final Executor mExecutor;
private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final int mExpandedTaskBarHeight;
private final ShellController mShellController;
private final Lazy<Optional<SplitScreenController>> mSplitScreenController;
private final UnfoldBackgroundController mUnfoldBackgroundController;
@@ -86,7 +86,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
private final Rect mRootStageBounds = new Rect();
private float mWindowCornerRadiusPx;
- private InsetsSource mTaskbarInsetsSource;
+ private InsetsSource mExpandedTaskbarInsetsSource;
@SplitPosition
private int mMainStagePosition = SPLIT_POSITION_UNDEFINED;
@@ -103,8 +103,6 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
mShellController = shellController;
mUnfoldBackgroundController = unfoldBackgroundController;
mSplitScreenController = splitScreenController;
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
@@ -143,10 +141,21 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
updateContexts();
}
+ private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ return source;
+ }
+ }
+ return null;
+ }
+
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
final AnimationContext context = mAnimationContextByTaskId.get(taskId);
@@ -307,7 +316,8 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
boolean taskbarExpanded = isTaskbarExpanded();
if (taskbarExpanded) {
// Only insets the cropping window with taskbar when taskbar is expanded
- mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
+ mStartCropRect.inset(mExpandedTaskbarInsetsSource.calculateVisibleInsets(
+ mStartCropRect));
}
// Offset to surface coordinates as layout bounds are in screen coordinates
@@ -360,8 +370,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
}
private boolean isTaskbarExpanded() {
- return mTaskbarInsetsSource != null
- && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
+ return mExpandedTaskbarInsetsSource != null;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
new file mode 100644
index 000000000000..7595c9617709
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.SparseBooleanArray;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+
+import java.util.function.Predicate;
+
+/** Various utility functions for transitions. */
+public class TransitionUtil {
+
+ /** @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
+ || type == TRANSIT_TO_FRONT
+ || type == TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+
+ /** @return true if the transition was triggered by closing something vs opening something */
+ public static boolean isClosingType(@WindowManager.TransitionType int type) {
+ return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
+ }
+
+ /** Returns {@code true} if the transition has a display change. */
+ public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Returns `true` if `change` is a wallpaper. */
+ public static boolean isWallpaper(TransitionInfo.Change change) {
+ return (change.getTaskInfo() == null)
+ && change.hasFlags(FLAG_IS_WALLPAPER)
+ && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
+ /** Returns `true` if `change` is not an app window or wallpaper. */
+ public static boolean isNonApp(TransitionInfo.Change change) {
+ return (change.getTaskInfo() == null)
+ && !change.hasFlags(FLAG_IS_WALLPAPER)
+ && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
+ /**
+ * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
+ * MUST call `test` in the same order that the changes appear in the TransitionInfo.
+ */
+ public static class LeafTaskFilter implements Predicate<TransitionInfo.Change> {
+ private final SparseBooleanArray mChildTaskTargets = new SparseBooleanArray();
+
+ @Override
+ public boolean test(TransitionInfo.Change change) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // Children always come before parent since changes are in top-to-bottom z-order.
+ if ((taskInfo == null) || mChildTaskTargets.get(taskInfo.taskId)) {
+ // has children, so not a leaf. Skip.
+ return false;
+ }
+ if (taskInfo.hasParentTask()) {
+ mChildTaskTargets.put(taskInfo.parentTaskId, true);
+ }
+ return true;
+ }
+ }
+
+
+ private static int newModeToLegacyMode(int newMode) {
+ switch (newMode) {
+ case WindowManager.TRANSIT_OPEN:
+ case WindowManager.TRANSIT_TO_FRONT:
+ return MODE_OPENING;
+ case WindowManager.TRANSIT_CLOSE:
+ case WindowManager.TRANSIT_TO_BACK:
+ return MODE_CLOSING;
+ default:
+ return MODE_CHANGING;
+ }
+ }
+
+ /**
+ * Very similar to Transitions#setupAnimHierarchy but specialized for leashes.
+ */
+ @SuppressLint("NewApi")
+ private static void setupLeash(@NonNull SurfaceControl leash,
+ @NonNull TransitionInfo.Change change, int layer,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ final boolean isOpening = TransitionUtil.isOpeningType(info.getType());
+ // Put animating stuff above this line and put static stuff below it.
+ int zSplitLine = info.getChanges().size();
+ // changes should be ordered top-to-bottom in z
+ final int mode = change.getMode();
+
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ final Rect absBounds =
+ (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
+ t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+ absBounds.top - info.getRoot(rootIdx).getOffset().y);
+
+ // Put all the OPEN/SHOW on top
+ if (TransitionUtil.isOpeningType(mode)) {
+ if (isOpening) {
+ t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
+ if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
+ // if transferred, it should be left visible.
+ t.setAlpha(leash, 0.f);
+ }
+ } else {
+ // put on bottom and leave it visible
+ t.setLayer(leash, zSplitLine - layer);
+ }
+ } else if (TransitionUtil.isClosingType(mode)) {
+ if (isOpening) {
+ // put on bottom and leave visible
+ t.setLayer(leash, zSplitLine - layer);
+ } else {
+ // put on top
+ t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
+ }
+ } else { // CHANGE
+ t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private static SurfaceControl createLeash(TransitionInfo info, TransitionInfo.Change change,
+ int order, SurfaceControl.Transaction t) {
+ // TODO: once we can properly sync transactions across process, then get rid of this leash.
+ if (change.getParent() != null && (change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ // Special case for wallpaper atm. Normally these are left alone; but, a quirk of
+ // making leashes means we have to handle them specially.
+ return change.getLeash();
+ }
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ SurfaceControl leashSurface = new SurfaceControl.Builder()
+ .setName(change.getLeash().toString() + "_transition-leash")
+ .setContainerLayer()
+ // Initial the surface visible to respect the visibility of the original surface.
+ .setHidden(false)
+ .setParent(info.getRoot(rootIdx).getLeash())
+ .build();
+ // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
+ setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
+ t.reparent(change.getLeash(), leashSurface);
+ t.setAlpha(change.getLeash(), 1.0f);
+ t.show(change.getLeash());
+ t.setPosition(change.getLeash(), 0, 0);
+ t.setLayer(change.getLeash(), 0);
+ return leashSurface;
+ }
+
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change info
+ */
+ public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+ TransitionInfo info, SurfaceControl.Transaction t,
+ @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ final SurfaceControl leash = createLeash(info, change, order, t);
+ if (leashMap != null) {
+ leashMap.put(change.getLeash(), leash);
+ }
+ return newTarget(change, order, leash);
+ }
+
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+ SurfaceControl leash) {
+ int taskId;
+ boolean isNotInRecents;
+ ActivityManager.RunningTaskInfo taskInfo;
+ WindowConfiguration windowConfiguration;
+
+ taskInfo = change.getTaskInfo();
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ windowConfiguration = new WindowConfiguration();
+ }
+
+ Rect localBounds = new Rect(change.getEndAbsBounds());
+ localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
+
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(change.getMode()),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ leash,
+ (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(change.getEndAbsBounds()),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(change.getStartAbsBounds()),
+ taskInfo,
+ change.getAllowEnterPip(),
+ (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
+ ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE
+ );
+ target.setWillShowImeOnTarget(
+ (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0);
+ target.setRotationChange(change.getEndRotation() - change.getStartRotation());
+ return target;
+ }
+
+ /**
+ * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+ * the start display. If there is no display, it will fallback on the 0th root in the
+ * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+ */
+ public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo info) {
+ int rootIdx = info.findRootIndex(change.getEndDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ rootIdx = info.findRootIndex(change.getStartDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ return 0;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index dee5f8fa74d8..6b45149e35a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -541,7 +541,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
private void createInputChannel(int displayId) {
- final InputManager inputManager = InputManager.getInstance();
+ final InputManager inputManager = mContext.getSystemService(InputManager.class);
final InputMonitor inputMonitor =
mInputMonitorFactory.create(inputManager, mContext);
final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 81c4176b0f39..8cb575cc96e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -103,7 +103,9 @@ class DragResizeInputListener implements AutoCloseable {
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
+ 0 /* inputFeatures */,
TYPE_APPLICATION,
+ null /* windowToken */,
mFocusGrantToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
@@ -207,6 +209,7 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
+ 0 /* inputFeatures */,
touchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index aea340464304..d0fcd8651481 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -65,7 +65,7 @@ class TaskOperations {
InputDevice.SOURCE_KEYBOARD);
ev.setDisplayId(mContext.getDisplay().getDisplayId());
- if (!InputManager.getInstance()
+ if (!mContext.getSystemService(InputManager.class)
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
Log.e(TAG, "Inject input event fail");
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index f8e6ecc4499a..ddd3b440e1a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -496,7 +496,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
- return new SurfaceControlViewHost(c, d, wmm);
+ return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 3ca5b9c38aff..b6696c70dbb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -41,6 +41,8 @@ android_test {
static_libs: [
"androidx.test.ext.junit",
"flickerlib",
+ "flickerlib-apphelpers",
+ "flickerlib-helpers",
"truth-prebuilt",
"app-helpers-core",
"launcher-helper-lib",
@@ -48,6 +50,6 @@ android_test {
"wm-flicker-common-assertions",
"wm-flicker-common-app-helpers",
"platform-test-annotations",
- "wmshell-flicker-test-components",
+ "flickertestapplib",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 06df9568e01a..4721741611cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -46,7 +46,7 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true">
+ <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 574a9f4da627..67ca9a1a17f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,13 +13,27 @@
<option name="run-command" value="cmd window tracing level all" />
<!-- set WM tracing to frame (avoid incomplete states) -->
<option name="run-command" value="cmd window tracing frame" />
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+ <!-- Ensure output directory is empty at the start -->
+ <option name="run-command" value="rm -rf /sdcard/flicker" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
+ <option name="run-command" value="settings put system show_touches 1" />
+ <option name="run-command" value="settings put system pointer_location 1" />
+ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+ <option name="teardown-command" value="settings delete system show_touches" />
+ <option name="teardown-command" value="settings delete system pointer_location" />
</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"/>
- <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
+ <option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.wm.shell.flicker"/>
@@ -32,4 +46,4 @@
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
-</configuration> \ No newline at end of file
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
new file mode 100644
index 000000000000..c5ee7b722617
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import org.junit.Assume
+import org.junit.Test
+
+/**
+ * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
+ * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions
+ * (layers visible in consecutive states, entire screen covered, etc.)
+ */
+abstract class BaseTest
+@JvmOverloads
+constructor(
+ protected val flicker: FlickerTest,
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
+) {
+ /** Specification of the test transition to execute */
+ abstract val transition: FlickerBuilder.() -> Unit
+
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache flicker
+ * executions
+ */
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup { flicker.scenario.setIsTablet(tapl.isTablet) }
+ transition()
+ }
+ }
+
+ /** Checks that all parts of the screen are covered during the transition */
+ @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+ */
+ @Presubmit
+ @Test
+ open fun navBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ open fun navBarLayerPositionAtStartAndEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+ *
+ * Note: Phones only
+ */
+ @Presubmit
+ @Test
+ open fun navBarWindowIsAlwaysVisible() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+ */
+ @Presubmit
+ @Test
+ open fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+ *
+ * Note: Large screen only
+ */
+ @Presubmit
+ @Test
+ open fun taskBarWindowIsAlwaysVisible() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+ * transition
+ */
+ @Presubmit
+ @Test
+ open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+ * transition
+ */
+ @Presubmit
+ @Test
+ open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Presubmit
+ @Test
+ open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ }
+
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Presubmit
+ @Test
+ open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ }
+}
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 cba396a82a87..ed93045ec462 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
@@ -15,27 +15,26 @@
*/
@file:JvmName("CommonAssertions")
+
package com.android.wm.shell.flicker
-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
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Region
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.helpers.WindowUtils
-fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
- assertLayersEnd {
- this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
- }
+fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
+ assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
- assertLayersEnd {
- this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
- }
+fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
+ assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerBecomesVisible() {
+fun FlickerTest.appPairsDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -43,91 +42,313 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() {
}
}
-fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
- layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+fun FlickerTest.splitScreenEntered(
+ component1: IComponentMatcher,
+ component2: IComponentMatcher,
+ fromOtherApp: Boolean,
+ appExistAtStart: Boolean = true
+) {
+ if (fromOtherApp) {
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component1)
+ } else {
+ appWindowIsNotContainAtStart(component1)
+ }
+ } else {
+ appWindowIsVisibleAtStart(component1)
+ }
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component2)
+ } else {
+ appWindowIsNotContainAtStart(component2)
+ }
+ splitScreenDividerIsInvisibleAtStart()
+
+ appWindowIsVisibleAtEnd(component1)
+ appWindowIsVisibleAtEnd(component2)
+ splitScreenDividerIsVisibleAtEnd()
}
-fun FlickerTestParameter.layerBecomesVisible(
- component: FlickerComponentName
+fun FlickerTest.splitScreenDismissed(
+ component1: IComponentMatcher,
+ component2: IComponentMatcher,
+ toHome: Boolean
) {
+ appWindowIsVisibleAtStart(component1)
+ appWindowIsVisibleAtStart(component2)
+ splitScreenDividerIsVisibleAtStart()
+
+ appWindowIsInvisibleAtEnd(component1)
+ if (toHome) {
+ appWindowIsInvisibleAtEnd(component2)
+ } else {
+ appWindowIsVisibleAtEnd(component2)
+ }
+ splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
+ assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
+ assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
+ assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
+ assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTest.splitScreenDividerBecomesVisible() {
+ layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+}
+
+fun FlickerTest.splitScreenDividerBecomesInvisible() {
assertLayers {
- this.isInvisible(component)
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
- .isVisible(component)
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
}
-fun FlickerTestParameter.layerIsVisibleAtEnd(
- component: FlickerComponentName
+fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
+ assertLayers { this.isInvisible(component).then().isVisible(component) }
+}
+
+fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
+ assertLayers { this.isVisible(component).then().isInvisible(component) }
+}
+
+fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
+ assertLayersEnd { this.isVisible(component) }
+}
+
+fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
+ assertLayers { this.isVisible(component) }
+}
+
+fun FlickerTest.splitAppLayerBoundsBecomesVisible(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
- assertLayersEnd {
- this.isVisible(component)
+ assertLayers {
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ }
+}
+
+fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+ assertLayers {
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ .then()
+ // TODO(b/245472831): Verify the component should snap to divider.
+ .isVisible(component)
}
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
- rotation: Int,
- component: FlickerComponentName,
- splitLeftTop: Boolean
+fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayers {
- val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- this.isInvisible(component)
+ this.splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
.then()
- .invoke("splitAppLayerBoundsBecomesVisible") {
- it.visibleRegion(component).overlaps(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
- } else {
- getSplitRightBottomRegion(dividerRegion, rotation)
- }
- )
- }
+ .isVisible(component, true)
+ .then()
+ .isInvisible(component)
}
}
-fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
- rotation: Int,
- component: FlickerComponentName,
- splitLeftTop: Boolean
+fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayersEnd {
- val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(component).overlaps(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
- } else {
- getSplitRightBottomRegion(dividerRegion, rotation)
- }
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ }
+}
+
+fun FlickerTest.splitAppLayerBoundsKeepVisible(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
+) {
+ assertLayers {
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
)
}
}
-fun FlickerTestParameter.appWindowBecomesVisible(
- component: FlickerComponentName
+fun FlickerTest.splitAppLayerBoundsChanges(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
+ assertLayers {
+ if (landscapePosLeft) {
+ this.splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ } else {
+ this.splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ }
+ }
+}
+
+fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean,
+ rotation: Rotation
+): LayersTraceSubject {
+ return invoke("splitAppLayerBoundsSnapToDivider") {
+ it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
+ }
+}
+
+fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean,
+ rotation: Rotation
+): LayerTraceEntrySubject {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return invoke {
+ val dividerRegion =
+ layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
+ ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+ visibleRegion(component)
+ .coversAtMost(
+ if (displayBounds.width > displayBounds.height) {
+ if (landscapePosLeft) {
+ Region.from(
+ 0,
+ 0,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ displayBounds.bounds.bottom
+ )
+ } else {
+ Region.from(
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ 0,
+ displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
+ } else {
+ if (portraitPosTop) {
+ Region.from(
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2
+ )
+ } else {
+ Region.from(
+ 0,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+ displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
+ }
+ )
+ }
+}
+
+fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
assertWm {
this.isAppWindowInvisible(component)
.then()
+ .notContains(component, isOptional = true)
+ .then()
+ .isAppWindowInvisible(component, isOptional = true)
+ .then()
.isAppWindowVisible(component)
}
}
-fun FlickerTestParameter.appWindowIsVisibleAtEnd(
- component: FlickerComponentName
-) {
- assertWmEnd {
- this.isAppWindowVisible(component)
- }
+fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
+ assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
- assertLayersEnd {
- this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
- }
+fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+ assertWmStart { this.isAppWindowVisible(component) }
+}
+
+fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+ assertWmEnd { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
+fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+ assertWmStart { this.isAppWindowInvisible(component) }
+}
+
+fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+ assertWmEnd { this.isAppWindowInvisible(component) }
+}
+
+fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+ assertWmStart { this.notContains(component) }
+}
+
+fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
+ assertWm { this.isAppWindowVisible(component) }
+}
+
+fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
+ assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
+}
+
+fun FlickerTest.dockedStackDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -135,7 +356,7 @@ fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
}
}
-fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
+fun FlickerTest.dockedStackDividerBecomesInvisible() {
assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -143,105 +364,91 @@ fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
}
}
-fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
- assertLayersEnd {
- this.notContains(DOCKED_STACK_DIVIDER_COMPONENT)
- }
+fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
+ assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
- primaryComponent: FlickerComponentName
+fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+ rotation: Rotation,
+ primaryComponent: IComponentMatcher
) {
assertLayersEnd {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryComponent)
- .overlaps(getPrimaryRegion(dividerRegion, rotation))
+ val dividerRegion =
+ layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
+ ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
+ visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
- primaryComponent: FlickerComponentName
+fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+ rotation: Rotation,
+ primaryComponent: IComponentMatcher
) {
assertLayersEnd {
- val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryComponent)
- .overlaps(getPrimaryRegion(dividerRegion, rotation))
+ val dividerRegion =
+ layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
+ ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
+ visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
- secondaryComponent: FlickerComponentName
+fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+ rotation: Rotation,
+ secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(secondaryComponent)
- .overlaps(getSecondaryRegion(dividerRegion, rotation))
+ val dividerRegion =
+ layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
+ ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
+ visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
- secondaryComponent: FlickerComponentName
+fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+ rotation: Rotation,
+ secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
- val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(secondaryComponent)
- .overlaps(getSecondaryRegion(dividerRegion, rotation))
+ val dividerRegion =
+ layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
+ ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
+ visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
}
}
-fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(
- 0, 0, displayBounds.bounds.right,
- dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
- )
- } else {
+ return if (rotation.isRotated()) {
Region.from(
- 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ 0,
+ 0,
+ dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
displayBounds.bounds.bottom
)
- }
-}
-
-fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(
- 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.right, displayBounds.bounds.bottom
- )
} else {
Region.from(
- dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
- displayBounds.bounds.right, displayBounds.bounds.bottom
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
)
}
}
-fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (displayBounds.width > displayBounds.height) {
- Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
- } else {
- Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
- }
-}
-
-fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
+fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (displayBounds.width > displayBounds.height) {
+ return if (rotation.isRotated()) {
Region.from(
- dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+ dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
+ 0,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
} else {
Region.from(
- 0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+ 0,
+ dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index f56eb6e783aa..983640a70c4b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -15,11 +15,21 @@
*/
@file:JvmName("CommonConstants")
+
package com.android.wm.shell.flicker
-import com.android.server.wm.traces.common.FlickerComponentName
+import android.tools.common.datatypes.component.ComponentNameMatcher
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
-val SPLIT_SCREEN_DIVIDER_COMPONENT = FlickerComponentName("", "StageCoordinatorSplitDivider#")
+const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
+val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
+val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+enum class Direction {
+ UP,
+ DOWN,
+ LEFT,
+ RIGHT
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
new file mode 100644
index 000000000000..87b94ff8668b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import java.io.IOException
+
+object MultiWindowUtils {
+ private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+ try {
+ SystemUtil.runShellCommand(instrumentation, cmd)
+ } catch (e: IOException) {
+ Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+ }
+ }
+
+ fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+ )
+
+ fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+ Settings.Global.putInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ configValue
+ )
+
+ fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+ executeShellCommand(
+ instrumentation,
+ createConfigSupportsNonResizableMultiWindowCommand(configValue)
+ )
+
+ fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+ executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+ private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+ "wm set-multi-window-config --supportsNonResizable $configValue"
+
+ private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
index 51f7a18f60dd..e0ef92457f58 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
@@ -51,7 +51,7 @@ class NotificationListener : NotificationListenerService() {
private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
- "cmd notification disallow_listener %s"
+ "cmd notification disallow_listener %s"
private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener"
private var instance: NotificationListener? = null
@@ -79,25 +79,23 @@ class NotificationListener : NotificationListenerService() {
): StatusBarNotification? {
instance?.run {
return notifications.values.firstOrNull(predicate)
- } ?: throw IllegalStateException("NotificationListenerService is not connected")
+ }
+ ?: throw IllegalStateException("NotificationListenerService is not connected")
}
fun waitForNotificationToAppear(
predicate: (StatusBarNotification) -> Boolean
): StatusBarNotification? {
instance?.let {
- return waitForResult(extractor = {
- it.notifications.values.firstOrNull(predicate)
- }).second
- } ?: throw IllegalStateException("NotificationListenerService is not connected")
+ return waitForResult(extractor = { it.notifications.values.firstOrNull(predicate) })
+ .second
+ }
+ ?: throw IllegalStateException("NotificationListenerService is not connected")
}
- fun waitForNotificationToDisappear(
- predicate: (StatusBarNotification) -> Boolean
- ): Boolean {
- return instance?.let {
- wait { it.notifications.values.none(predicate) }
- } ?: throw IllegalStateException("NotificationListenerService is not connected")
+ fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean {
+ return instance?.let { wait { it.notifications.values.none(predicate) } }
+ ?: throw IllegalStateException("NotificationListenerService is not connected")
}
}
-} \ 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 4d87ec9e872f..556cb06f3ca1 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
@@ -15,6 +15,7 @@
*/
@file:JvmName("WaitUtils")
+
package com.android.wm.shell.flicker
import android.os.SystemClock
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 278ba9b0f4db..bab81d79c804 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
@@ -17,40 +17,38 @@
package com.android.wm.shell.flicker.bubble
import android.app.INotificationManager
-import android.app.Instrumentation
import android.app.NotificationManager
import android.content.Context
+import android.content.pm.PackageManager
import android.os.ServiceManager
-import android.view.Surface
-import androidx.test.platform.app.InstrumentationRegistry
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.helpers.SYSTEMUI_PACKAGE
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.wm.shell.flicker.helpers.LaunchBubbleHelper
+import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
+import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
-/**
- * Base configurations for Bubble flicker tests
- */
-abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
+/** Base configurations for Bubble flicker tests */
+abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val context: Context = instrumentation.context
protected val testApp = LaunchBubbleHelper(instrumentation)
- protected val notifyManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE))
-
- protected val uid = context.packageManager.getApplicationInfo(
- testApp.component.packageName, 0).uid
+ private val notifyManager =
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE)
+ )
- protected abstract val transition: FlickerBuilder.() -> Unit
+ private val uid =
+ context.packageManager
+ .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0))
+ .uid
@JvmOverloads
protected open fun buildTransition(
@@ -58,46 +56,41 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
): FlickerBuilder.() -> Unit {
return {
setup {
- test {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
- uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
- testApp.launchViaIntent(wmHelper)
- waitAndGetAddBubbleBtn()
- waitAndGetCancelAllBtn()
- }
+ notifyManager.setBubblesAllowed(
+ testApp.`package`,
+ uid,
+ NotificationManager.BUBBLE_PREFERENCE_ALL
+ )
+ testApp.launchViaIntent(wmHelper)
+ waitAndGetAddBubbleBtn()
+ waitAndGetCancelAllBtn()
}
teardown {
- test {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
- uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
- testApp.exit()
- }
+ notifyManager.setBubblesAllowed(
+ testApp.`package`,
+ uid,
+ NotificationManager.BUBBLE_PREFERENCE_NONE
+ )
+ testApp.exit()
}
extraSpec(this)
}
}
- 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 {
- transition(this)
- }
- }
+ protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
+ device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+ protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
+ device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
}
const val FIND_OBJECT_TIMEOUT = 2000L
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/ChangeActiveActivityFromBubbleTest.kt
index 8d1e315e2d5e..d0bca1332553 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/ChangeActiveActivityFromBubbleTest.kt
@@ -18,18 +18,18 @@ package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
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.Before
-import org.junit.runner.RunWith
import org.junit.Test
+import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
@@ -38,38 +38,47 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
*
* Actions:
+ * ```
* Switch in different bubble notifications
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
@Before
open fun before() {
Assume.assumeFalse(isShellTransitionsEnabled)
}
+ /** {@inheritDoc} */
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(
- By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
- showBubble?.run { showBubble.click() } ?: error("Show bubble not found")
+ for (i in 1..3) {
+ val addBubbleBtn = waitAndGetAddBubbleBtn() ?: error("Add Bubble not found")
+ addBubbleBtn.click()
SystemClock.sleep(1000)
}
+ val showBubble =
+ device.wait(
+ Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+ FIND_OBJECT_TIMEOUT
+ )
+ ?: error("Show bubble not found")
+ showBubble.click()
+ SystemClock.sleep(1000)
}
transitions {
- val bubbles = device.wait(Until.findObjects(
- By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ val bubbles: List<UiObject2> =
+ device.wait(
+ Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+ FIND_OBJECT_TIMEOUT
+ )
+ ?: error("No bubbles found")
for (entry in bubbles) {
- entry?.run { entry.click() } ?: error("Bubble not found")
+ entry.click()
SystemClock.sleep(1000)
}
}
@@ -78,8 +87,6 @@ open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
new file mode 100644
index 000000000000..bdfdad59c600
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker)
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/ChangeActiveActivityFromBubbleTestShellTransit.kt
index ddebb6fed636..5e85eb87e0e9 100644
--- 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/ChangeActiveActivityFromBubbleTestShellTransit.kt
@@ -16,12 +16,11 @@
package com.android.wm.shell.flicker.bubble
-import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.FlakyTest
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import 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
@@ -30,13 +29,11 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
@FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(
- testSpec: FlickerTestParameter
-) : MultiBubblesScreen(testSpec) {
+class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker) {
@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/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index b137e92881a5..8474ce0e64e5 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/DragToDismissBubbleScreenTest.kt
@@ -19,17 +19,16 @@ package com.android.wm.shell.flicker.bubble
import android.content.Context
import android.graphics.Point
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import android.util.DisplayMetrics
import android.view.WindowManager
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 org.junit.runner.RunWith
import org.junit.Test
+import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
@@ -38,30 +37,33 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
*
* Actions:
+ * ```
* Dismiss a bubble notification
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
setup {
- eachRun {
- val addBubbleBtn = waitAndGetAddBubbleBtn()
- addBubbleBtn?.click() ?: error("Add Bubble not found")
- }
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Add Bubble not found")
}
transitions {
- wm.run { wm.getDefaultDisplay().getMetrics(displaySize) }
+ wm.run { wm.defaultDisplay.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)
+ 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")
}
}
@@ -69,8 +71,6 @@ open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScree
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
index 9c82eea1e8b8..62fa7b4516c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+package com.android.wm.shell.flicker.bubble
-import android.app.Activity;
-import android.os.Bundle;
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
-public class SplitScreenActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.activity_splitscreen);
- }
-}
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
+ DragToDismissBubbleScreenTest(flicker)
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
deleted file mode 100644
index 684e5cad0e67..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.bubble
-
-import android.platform.test.annotations.Presubmit
-import android.view.WindowInsets
-import android.view.WindowManager
-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 {
- // Swipe & wait for the notification shade to expand so all can be seen
- val wm = context.getSystemService(WindowManager::class.java)
- val metricInsets = wm.getCurrentWindowMetrics().windowInsets
- val insets = metricInsets.getInsetsIgnoringVisibility(
- WindowInsets.Type.statusBars()
- or WindowInsets.Type.displayCutout())
- device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4)
- device.waitForIdle(2000)
- instrumentation.uiAutomation.syncInputTransactions()
-
- 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/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
new file mode 100644
index 000000000000..416315e4b06d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import org.junit.Assume
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest`
+ *
+ * Actions:
+ * ```
+ * Launch an bubble from notification on lock screen
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
+ setup {
+ val addBubbleBtn = waitAndGetAddBubbleBtn()
+ addBubbleBtn?.click() ?: error("Bubble widget not found")
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ device.wakeUp()
+ }
+ transitions {
+ // Swipe & wait for the notification shade to expand so all can be seen
+ val wm =
+ context.getSystemService(WindowManager::class.java)
+ ?: error("Unable to obtain WM service")
+ val metricInsets = wm.currentWindowMetrics.windowInsets
+ val insets =
+ metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+ )
+ device.swipe(100, insets.top + 100, 100, device.displayHeight / 2, 4)
+ device.waitForIdle(2000)
+ instrumentation.uiAutomation.syncInputTransactions()
+
+ 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")
+ }
+ }
+
+ @FlakyTest(bugId = 242088970)
+ @Test
+ fun testAppIsVisibleAtEnd() {
+ flicker.assertLayersEnd { this.isVisible(testApp) }
+ }
+
+ @Postsubmit
+ @Test
+ fun navBarLayerIsVisibleAtEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtEnd()
+ }
+
+ @Postsubmit
+ @Test
+ fun navBarLayerPositionAtEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtEnd()
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarWindowIsAlwaysVisible()
+ }
+}
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/OpenActivityFromBubbleTest.kt
index f288b0a24d9d..07ba41333071 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/OpenActivityFromBubbleTest.kt
@@ -17,15 +17,14 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
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 org.junit.runner.RunWith
import org.junit.Test
+import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
@@ -34,27 +33,30 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
*
* Actions:
+ * ```
* Launch an app and enable app's bubble notification
* Send a bubble notification
* The activity for the bubble is launched
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
setup {
- test {
- val addBubbleBtn = waitAndGetAddBubbleBtn()
- addBubbleBtn?.click() ?: error("Add Bubble 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)
+ 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")
}
}
@@ -62,8 +64,6 @@ open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
index 471e010cf560..6c61710d6284 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.wm.shell.flicker.bubble
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
-class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.FixedActivity.LABEL,
- Components.FixedActivity.COMPONENT.toFlickerComponent()
-) \ No newline at end of file
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
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/SendBubbleNotificationTest.kt
index 0bb4d398bff4..29f76d01af83 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/SendBubbleNotificationTest.kt
@@ -17,11 +17,12 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
-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 android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -32,28 +33,34 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
*
* Actions:
+ * ```
* Launch an app and enable app's bubble notification
* Send a bubble notification
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
transitions {
val addBubbleBtn = waitAndGetAddBubbleBtn()
addBubbleBtn?.click() ?: error("Bubble widget not found")
+
+ device.wait(
+ Until.findObjects(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+ FIND_OBJECT_TIMEOUT
+ )
+ ?: error("No bubbles found")
}
}
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
index 4d0fbc4a0e38..e323ebf3b5c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.helpers
+package com.android.wm.shell.flicker.bubble
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
-class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.SimpleActivity.LABEL,
- Components.SimpleActivity.COMPONENT.toFlickerComponent()
-) \ No newline at end of file
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
+ SendBubbleNotificationTest(flicker)
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
deleted file mode 100644
index 41cd31aabf05..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-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): Region {
- val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right,
- dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
- return primaryAppBounds
- }
-
- fun getSecondaryBounds(dividerBounds: Region): Region {
- val displayBounds = WindowUtils.displayBounds
- val secondaryAppBounds = Region.from(0,
- dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
- return secondaryAppBounds
- }
-
- companion object {
- const val TEST_REPETITIONS = 1
- const val TIMEOUT_MS = 3_000L
-
- fun Flicker.waitAppsShown(app1: SplitScreenHelper?, app2: SplitScreenHelper?) {
- wmHelper.waitFor("primaryAndSecondaryAppsVisible") { dump ->
- val primaryAppVisible = app1?.let {
- dump.wmState.isWindowSurfaceShown(app1.defaultWindowName)
- } ?: false
- val secondaryAppVisible = app2?.let {
- dump.wmState.isWindowSurfaceShown(app2.defaultWindowName)
- } ?: false
- primaryAppVisible && secondaryAppVisible
- }
- }
- }
-}
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
deleted file mode 100644
index 3dd9e0572947..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
-import android.os.SystemProperties
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.util.Log
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.FlickerComponentName
-import java.io.IOException
-
-abstract class BaseAppHelper(
- instrumentation: Instrumentation,
- launcherName: String,
- component: FlickerComponentName
-) : StandardAppHelper(
- instrumentation,
- launcherName,
- component,
- LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
-) {
- private val appSelector = By.pkg(component.packageName).depth(0)
-
- protected val isTelevision: Boolean
- get() = context.packageManager.run {
- hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
- }
-
- val defaultWindowName: String
- get() = component.toWindowName()
-
- val ui: UiObject2?
- get() = uiDevice.findObject(appSelector)
-
- fun waitUntilClosed(): Boolean {
- return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
- }
-
- companion object {
- private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
-
- fun isShellTransitionsEnabled() =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false)
-
- fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
- try {
- SystemUtil.runShellCommand(instrumentation, cmd)
- } catch (e: IOException) {
- Log.e("BaseAppHelper", "executeShellCommand error! $e")
- }
- }
- }
-}
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
deleted file mode 100644
index cc5b9f9eb26d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.testapp.Components
-
-open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.ImeActivity.LABEL,
- Components.ImeActivity.COMPONENT.toFlickerComponent()
-) {
- /**
- * Opens the IME and wait for it to be displayed
- *
- * @param wmHelper Helper used to wait for WindowManager states
- */
- @JvmOverloads
- open fun openIME(wmHelper: WindowManagerStateHelper? = null) {
- if (!isTelevision) {
- val editText = uiDevice.wait(
- Until.findObject(By.res(getPackage(), "plain_text_input")),
- FIND_TIMEOUT)
-
- require(editText != null) {
- "Text field not found, this usually happens when the device " +
- "was left in an unknown state (e.g. in split screen)"
- }
- editText.click()
- waitAndAssertIMEShown(uiDevice, wmHelper)
- } else {
- // If we do the same thing as above - editText.click() - on TV, that's going to force TV
- // into the touch mode. We really don't want that.
- launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME)
- }
- }
-
- protected fun waitAndAssertIMEShown(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper? = null
- ) {
- if (wmHelper == null) {
- device.waitForIdle()
- } else {
- wmHelper.waitImeShown()
- }
- }
-
- /**
- * Opens the IME and wait for it to be gone
- *
- * @param wmHelper Helper used to wait for WindowManager states
- */
- @JvmOverloads
- open fun closeIME(wmHelper: WindowManagerStateHelper? = null) {
- if (!isTelevision) {
- uiDevice.pressBack()
- // Using only the AccessibilityInfo it is not possible to identify if the IME is active
- if (wmHelper == null) {
- uiDevice.waitForIdle()
- } else {
- wmHelper.waitImeGone()
- }
- } else {
- // While pressing the back button should close the IME on TV as well, it may also lead
- // to the app closing. So let's instead just ask the app to close the IME.
- launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
deleted file mode 100644
index 12ccbafce651..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.Context
-import android.provider.Settings
-import com.android.server.wm.traces.common.FlickerComponentName
-
-class MultiWindowHelper(
- instrumentation: Instrumentation,
- activityLabel: String,
- componentsInfo: FlickerComponentName
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
-
- companion object {
- fun getDevEnableNonResizableMultiWindow(context: Context): Int =
- Settings.Global.getInt(context.contentResolver,
- Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-
- fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
- Settings.Global.putInt(context.contentResolver,
- Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
-
- fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
- executeShellCommand(
- instrumentation,
- createConfigSupportsNonResizableMultiWindowCommand(configValue))
-
- fun resetMultiWindowConfig(instrumentation: Instrumentation) =
- executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
-
- private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
- "wm set-multi-window-config --supportsNonResizable $configValue"
-
- private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
- }
-}
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
deleted file mode 100644
index 8157a4e453af..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.media.session.MediaController
-import android.media.session.MediaSessionManager
-import android.os.SystemClock
-import androidx.test.uiautomator.By
-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
-import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
-import com.android.wm.shell.flicker.testapp.Components
-
-class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.PipActivity.LABEL,
- Components.PipActivity.COMPONENT.toFlickerComponent()
-) {
- private val mediaSessionManager: MediaSessionManager
- get() = context.getSystemService(MediaSessionManager::class.java)
- ?: error("Could not get MediaSessionManager")
-
- private val mediaController: MediaController?
- get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
- it.packageName == component.packageName
- }
-
- fun clickObject(resId: String) {
- val selector = By.res(component.packageName, resId)
- val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
- if (!isTelevision) {
- obj.click()
- } else {
- focusOnObject(selector) || error("Could not focus on `$resId` object")
- uiDevice.pressDPadCenter()
- }
- }
-
- /**
- * 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? = null,
- stringExtras: Map<String, String>
- ) {
- 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"
- // from "the bottom".
- repeat(FOCUS_ATTEMPTS) {
- uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
- ?: error("The object we try to focus on is gone.")
-
- uiDevice.pressDPadDown()
- uiDevice.waitForIdle()
- }
- return false
- }
-
- @JvmOverloads
- fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
- clickObject(ENTER_PIP_BUTTON_ID)
-
- // Wait on WMHelper or simply wait for 3 seconds
- wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
- // when entering pip, the dismiss button is visible at the start. to ensure the pip
- // animation is complete, wait until the pip dismiss button is no longer visible.
- // b/176822698: dismiss-only state will be removed in the future
- uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
- }
-
- fun enableEnterPipOnUserLeaveHint() {
- clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
- }
-
- fun enableAutoEnterForPipActivity() {
- clickObject(ENTER_PIP_AUTOENTER)
- }
-
- fun clickStartMediaSessionButton() {
- clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
- }
-
- fun checkWithCustomActionsCheckbox() = uiDevice
- .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
- ?.takeIf { it.isCheckable }
- ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
- ?: error("'With custom actions' checkbox not found")
-
- fun pauseMedia() = mediaController?.transportControls?.pause()
- ?: error("No active media session found")
-
- fun stopMedia() = mediaController?.transportControls?.stop()
- ?: error("No active media session found")
-
- @Deprecated(
- "Use PipAppHelper.closePipWindow(wmHelper) instead",
- ReplaceWith("closePipWindow(wmHelper)")
- )
- fun closePipWindow() {
- if (isTelevision) {
- uiDevice.closeTvPipWindow()
- } else {
- closePipWindow(WindowManagerStateHelper(mInstrumentation))
- }
- }
-
- private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
- val windowRegion = wmHelper.getWindowRegion(component)
- require(!windowRegion.isEmpty) {
- "Unable to find a PIP window in the current state"
- }
- return windowRegion.bounds
- }
-
- /**
- * Taps the pip window and dismisses it by clicking on the X button.
- */
- fun closePipWindow(wmHelper: WindowManagerStateHelper) {
- if (isTelevision) {
- uiDevice.closeTvPipWindow()
- } else {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- val dismissPipObject = uiDevice.findObject(dismissSelector)
- ?: error("PIP window dismiss button not found")
- val dismissButtonBounds = dismissPipObject.visibleBounds
- uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
- }
-
- // Wait for animation to complete.
- wmHelper.waitPipGone()
- wmHelper.waitForHomeActivityVisible()
- }
-
- /**
- * Close the pip window by pressing the expand button
- */
- fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the expand button
- val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
- uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
- val expandPipObject = uiDevice.findObject(expandSelector)
- ?: error("PIP window expand button not found")
- val expandButtonBounds = expandPipObject.visibleBounds
- uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
- wmHelper.waitPipGone()
- wmHelper.waitForAppTransitionIdle()
- }
-
- /**
- * Double click on the PIP window to expand it
- */
- fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- wmHelper.waitForAppTransitionIdle()
- }
-
- companion object {
- private const val FOCUS_ATTEMPTS = 20
- private const val ENTER_PIP_BUTTON_ID = "enter_pip"
- private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
- private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
- private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
- private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
deleted file mode 100644
index 49eca63a23ec..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.view.InputDevice
-import android.view.MotionEvent
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
-import org.junit.Assert
-
-class SplitScreenHelper(
- instrumentation: Instrumentation,
- activityLabel: String,
- componentsInfo: FlickerComponentName
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
-
- companion object {
- const val TEST_REPETITIONS = 1
- const val TIMEOUT_MS = 3_000L
- const val DRAG_DURATION_MS = 1_000L
- const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
- const val GESTURE_STEP_MS = 16L
-
- private val notificationScrollerSelector: BySelector
- get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
- private val notificationContentSelector: BySelector
- get() = By.text("Notification content")
-
- fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SplitScreenSecondaryActivity.LABEL,
- Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.NonResizeableActivity.LABEL,
- Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
- )
-
- fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(
- instrumentation,
- Components.SendNotificationActivity.LABEL,
- Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
- )
-
- fun dragFromNotificationToSplit(
- instrumentation: Instrumentation,
- device: UiDevice,
- wmHelper: WindowManagerStateHelper
- ) {
- val displayBounds = wmHelper.currentState.layerState
- .displays.firstOrNull { !it.isVirtual }
- ?.layerStackSpace
- ?: error("Display not found")
-
- // Pull down the notifications
- device.swipe(
- displayBounds.centerX(), 5,
- displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
- )
- SystemClock.sleep(TIMEOUT_MS)
-
- // Find the target notification
- val notificationScroller = device.wait(
- Until.findObject(notificationScrollerSelector), TIMEOUT_MS
- )
- var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
- while (notificationContent == null) {
- device.swipe(
- displayBounds.centerX(), displayBounds.centerY(),
- displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
- )
- notificationContent = notificationScroller.findObject(notificationContentSelector)
- }
-
- // Drag to split
- var dragStart = notificationContent.visibleCenter
- var dragMiddle = Point(dragStart.x + 50, dragStart.y)
- var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
- val downTime = SystemClock.uptimeMillis()
-
- touch(
- instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
- TIMEOUT_MS, dragStart
- )
- // It needs a horizontal movement to trigger the drag
- touchMove(
- instrumentation, downTime, SystemClock.uptimeMillis(),
- DRAG_DURATION_MS, dragStart, dragMiddle
- )
- touchMove(
- instrumentation, downTime, SystemClock.uptimeMillis(),
- DRAG_DURATION_MS, dragMiddle, dragEnd
- )
- // Wait for a while to start splitting
- SystemClock.sleep(TIMEOUT_MS)
- touch(
- instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
- GESTURE_STEP_MS, dragEnd
- )
- SystemClock.sleep(TIMEOUT_MS)
- }
-
- fun touch(
- instrumentation: Instrumentation,
- action: Int,
- downTime: Long,
- eventTime: Long,
- duration: Long,
- point: Point
- ) {
- val motionEvent = MotionEvent.obtain(
- downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
- )
- motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
- instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
- motionEvent.recycle()
- SystemClock.sleep(duration)
- }
-
- fun touchMove(
- instrumentation: Instrumentation,
- downTime: Long,
- eventTime: Long,
- duration: Long,
- from: Point,
- to: Point
- ) {
- val steps: Long = duration / GESTURE_STEP_MS
- var currentTime = eventTime
- var currentX = from.x.toFloat()
- var currentY = from.y.toFloat()
- val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
- val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
- for (i in 1..steps) {
- val motionMove = MotionEvent.obtain(
- downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
- )
- motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
- instrumentation.uiAutomation.injectInputEvent(motionMove, true)
- motionMove.recycle()
-
- currentTime += GESTURE_STEP_MS
- if (i == steps - 1) {
- currentX = to.x.toFloat()
- currentY = to.y.toFloat()
- } else {
- currentX += stepX
- currentY += stepY
- }
- SystemClock.sleep(GESTURE_STEP_MS)
- }
- }
-
- fun createShortcutOnHotseatIfNotExist(
- taplInstrumentation: LauncherInstrumentation,
- appName: String
- ) {
- taplInstrumentation.workspace
- .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
- val allApps = taplInstrumentation.workspace.switchToAllApps()
- allApps.freeze()
- try {
- val appIconSrc = allApps.getAppIcon(appName)
- Assert.assertNotNull("Unable to find app icon", appIconSrc)
- val appIconDest = appIconSrc.dragToHotseat(0)
- Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest)
- } finally {
- allApps.unfreeze()
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index ce624f2b5bbe..1045a5ac2ce8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -17,12 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,69 +35,60 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
*
* Actions:
+ * ```
* Launch an app in full screen
* Select "Auto-enter PiP" radio button
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
- *
+ * ```
* Notes:
+ * ```
* 1. All assertions are inherited from [EnterPipTest]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 238367575)
-@Group3
-class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
- protected val taplInstrumentation = LauncherInstrumentation()
- /**
- * Defines the transition used to run the test
- */
+class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
- setupAndTeardown(this)
setup {
- eachRun {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableAutoEnterForPipActivity()
- }
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableAutoEnterForPipActivity()
}
teardown {
- eachRun {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- }
- transitions {
- taplInstrumentation.goHome()
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
}
+ transitions { tapl.goHome() }
}
+ @FlakyTest(bugId = 256863309)
+ @Test
override fun pipLayerReduces() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
}
}
}
- /**
- * Checks that [pipApp] window is animated towards default position in right bottom corner
- */
+ /** Checks that [pipApp] window is animated towards default position in right bottom corner */
+ @Presubmit
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
- Assume.assumeFalse(testSpec.isGesturalNavigation)
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
// Pip animates towards the right bottom corner, but because it is being resized at the
// same time, it is possible it shrinks first quickly below the default position and get
// moved up after that in just few last frames
@@ -108,9 +98,11 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(
}
}
+ @Presubmit
+ @Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
}
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/ClosePipBySwipingDownTest.kt
index 128703ad332c..2d2588ef4348 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/ClosePipBySwipingDownTest.kt
@@ -17,14 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
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 org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,70 +34,64 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Swipe the pip window to the bottom-center of the screen and wait it disappear
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
- val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds
+ val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
val pipCenterX = pipRegion.centerX()
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
- device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10)
- wmHelper.waitPipGone()
- wmHelper.waitForWindowSurfaceDisappeared(pipApp.component)
- wmHelper.waitForAppTransitionIdle()
+ val barComponent =
+ if (flicker.scenario.isTablet) {
+ ComponentNameMatcher.TASK_BAR
+ } else {
+ ComponentNameMatcher.NAV_BAR
+ }
+ val barLayerHeight =
+ wmHelper.currentState.layerState
+ .getLayerWithBuffer(barComponent)
+ ?.visibleRegion
+ ?.height
+ ?: error("Couldn't find Nav or Task bar layer")
+ // The dismiss button doesn't appear at the complete bottom of the screen,
+ // it appears above the hot seat but `hotseatBarSize` is not available outside
+ // the platform
+ val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
+ device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
+ // Wait until the other app is no longer visible
+ wmHelper
+ .StateSyncBuilder()
+ .withPipGone()
+ .withWindowSurfaceDisappeared(pipApp)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
}
- @FlakyTest
- @Test
- override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
-
- @FlakyTest
- @Test
- override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
-
- /**
- * Checks that the focus doesn't change between windows during the transition
- */
+ /** Checks that the focus doesn't change between windows during the transition */
@Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
- }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
new file mode 100644
index 000000000000..02f60100d069
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
new file mode 100644
index 000000000000..6c5a344c8f79
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -0,0 +1,98 @@
+/*
+ * 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 android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+/** Base class for exiting pip (closing pip window) without returning to the app */
+abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
+ setup { this.setRotation(flicker.scenario.startRotation) }
+ teardown { this.setRotation(Rotation.ROTATION_0) }
+ }
+
+ /**
+ * Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and
+ * invisible at the same moment, and remains unpinned and invisible until the end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ open fun pipWindowBecomesInvisible() {
+ if (isShellTransitionsEnabled) {
+ // When Shell transition is enabled, we change the windowing mode at start, but
+ // update the visibility after the transition is finished, so we can't check isNotPinned
+ // and isAppWindowInvisible in the same assertion block.
+ flicker.assertWm {
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
+ }
+ .then()
+ .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
+ }
+ flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
+ } else {
+ flicker.assertWm {
+ this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
+ .then()
+ .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
+ }
+ }
+ }
+
+ /**
+ * Checks that [pipApp] and [LAUNCHER] layers are visible at the start of the transition. Then
+ * [pipApp] layer becomes invisible, and remains invisible until the end of the transition
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerBecomesInvisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ .isVisible(LAUNCHER)
+ .then()
+ .isInvisible(pipApp)
+ .isVisible(LAUNCHER)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 437ad893f1d9..e540ad543228 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/ClosePipWithDismissButtonTest.kt
@@ -17,14 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import 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 org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,63 +33,40 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Click on the pip window
* Click on dismiss button and wait window disappear
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- transitions {
- pipApp.closePipWindow(wmHelper)
- }
+ 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.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
- }
+ fun focusChanges() {
+ flicker.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
new file mode 100644
index 000000000000..05262feceba5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) :
+ ClosePipWithDismissButtonTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
deleted file mode 100644
index f9b08000290f..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker.pip
-
-internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 953f59a1f70b..f52e877ec2b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -16,14 +16,14 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.Assume
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -34,78 +34,121 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
*
* Actions:
+ * ```
* Launch an app in full screen
* Select "Via code behind" radio button
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
- *
- * Notes:
- * 1. All assertions are inherited from [EnterPipTest]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
- protected val taplInstrumentation = LauncherInstrumentation()
- /**
- * Defines the transition used to run the test
- */
+open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
- setupAndTeardown(this)
setup {
- eachRun {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableEnterPipOnUserLeaveHint()
- }
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableEnterPipOnUserLeaveHint()
}
teardown {
- eachRun {
- pipApp.exit(wmHelper)
- }
- }
- transitions {
- taplInstrumentation.goHome()
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
}
+ transitions { tapl.goHome() }
}
+ @Presubmit
+ @Test
+ override fun pipAppWindowAlwaysVisible() {
+ // In gestural nav the pip will first move behind home and then above home. The visual
+ // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+ // But the internal states of activity don't need to follow that, such as a temporary
+ // visibility state can be changed quickly outside a transaction so the test doesn't
+ // detect that. Hence, skip the case to avoid restricting the internal implementation.
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppWindowAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
override fun pipAppLayerAlwaysVisible() {
- if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else {
- // pip layer in gesture nav will disappear during transition
- testSpec.assertLayers {
- this.isVisible(pipApp.component)
- .then().isInvisible(pipApp.component)
- .then().isVisible(pipApp.component)
- }
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppLayerAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // no overlay in gesture nav for non-auto enter PiP transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipOverlayLayerAppearThenDisappear()
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppWindowVisibleChanges() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertWm {
+ this.isAppWindowVisible(pipApp)
+ .then()
+ .isAppWindowInvisible(pipApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(pipApp, isOptional = true)
}
}
+ @Presubmit
+ @Test
+ fun pipAppLayerVisibleChanges() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayers {
+ this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
+ }
+ }
+
+ @Presubmit
+ @Test
override fun pipLayerReduces() {
// in gestural nav the pip enters through alpha animation
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.pipLayerReduces()
}
+ @Presubmit
+ @Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
- override fun pipLayerRemainInsideVisibleBounds() {
- if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else {
- // pip layer in gesture nav will disappear during transition
- testSpec.assertLayersStart {
- this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
- }
- testSpec.assertLayersEnd {
- this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
- }
- }
+ @Presubmit
+ @Test
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerOrOverlayRemainInsideVisibleBounds()
+ }
+
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
new file mode 100644
index 000000000000..90f99c0c4cae
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** This test will fail because of b/264261596 */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker)
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
deleted file mode 100644
index 61ac49835185..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.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.LAUNCHER_COMPONENT
-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
-
-/**
- * Test entering pip from an app by interacting with the app UI
- *
- * To run this test: `atest WMShellFlickerTests:EnterPipTest`
- *
- * Actions:
- * Launch an app in full screen
- * Press an "enter pip" button to put [pipApp] in pip mode
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited from [PipTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-
- /**
- * Defines the transition used to run the test
- */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setupAndTeardown(this)
- setup {
- eachRun {
- pipApp.launchViaIntent(wmHelper)
- }
- }
- teardown {
- eachRun {
- pipApp.exit(wmHelper)
- }
- }
- transitions {
- pipApp.clickEnterPipButton(wmHelper)
- }
- }
-
- /**
- * Checks [pipApp] window remains visible throughout the animation
- */
- @Presubmit
- @Test
- fun pipAppWindowAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(pipApp.component)
- }
- }
-
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
- @Presubmit
- @Test
- open fun pipAppLayerAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(pipApp.component)
- }
- }
-
- /**
- * Checks that the pip app window remains inside the display bounds throughout the whole
- * animation
- */
- @Presubmit
- @Test
- fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- /**
- * Checks that the pip app layer remains inside the display bounds throughout the whole
- * animation
- */
- @Presubmit
- @Test
- open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- /**
- * Checks that the visible region of [pipApp] always reduces during the animation
- */
- @Presubmit
- @Test
- open fun pipLayerReduces() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
- pipLayerList.zipWithNext { previous, current ->
- current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
- }
- }
- }
-
- /**
- * Checks that [pipApp] window becomes pinned
- */
- @Presubmit
- @Test
- fun pipWindowBecomesPinned() {
- testSpec.assertWm {
- invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.component) }
- .then()
- .invoke("pipWindowIsPinned") { it.isPinned(pipApp.component) }
- }
- }
-
- /**
- * Checks [LAUNCHER_COMPONENT] layer remains visible throughout the animation
- */
- @Presubmit
- @Test
- fun launcherLayerBecomesVisible() {
- testSpec.assertLayers {
- isInvisible(LAUNCHER_COMPONENT)
- .then()
- .isVisible(LAUNCHER_COMPONENT)
- }
- }
-
- /**
- * Checks that the focus changes between the [pipApp] window and the launcher when
- * closing the pip window
- */
- @Presubmit
- @Test
- open fun focusChanges() {
- testSpec.assertEventLog {
- this.focusChanges(pipApp.`package`, "NexusLauncherActivity")
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
new file mode 100644
index 000000000000..e079d5477e2f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -0,0 +1,220 @@
+/*
+ * 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 android.app.Activity
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip while changing orientation (from app in landscape to pip window in portrait)
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
+ *
+ * Actions:
+ * ```
+ * Launch [testApp] on a fixed portrait orientation
+ * Launch [pipApp] on a fixed landscape orientation
+ * Broadcast action [ACTION_ENTER_PIP] to enter pip mode
+ * ```
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) {
+ private val testApp = FixedOrientationAppHelper(instrumentation)
+ private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
+ private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ // Launch a portrait only app on the fullscreen stack
+ testApp.launchViaIntent(
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
+ )
+ // Launch the PiP activity fixed as landscape
+ pipApp.launchViaIntent(
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
+ }
+ teardown {
+ pipApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+ transitions {
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back
+ // in portrait
+ broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+ // during rotation the status bar becomes invisible and reappears at the end
+ wmHelper
+ .StateSyncBuilder()
+ .withPipShown()
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ /**
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ */
+ @Before
+ fun setup() {
+ Assume.assumeFalse(tapl.isTablet)
+ }
+
+ /**
+ * Checks that all parts of the screen are covered at the start and end of the transition
+ *
+ * TODO b/197726599 Prevents all states from being checked
+ */
+ @Presubmit
+ @Test
+ fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
+
+ /** Checks [pipApp] window remains visible and on top throughout the transition */
+ @Presubmit
+ @Test
+ fun pipAppWindowIsAlwaysOnTop() {
+ flicker.assertWm { isAppWindowOnTop(pipApp) }
+ }
+
+ /** Checks that [testApp] window is not visible at the start */
+ @Presubmit
+ @Test
+ fun testAppWindowInvisibleOnStart() {
+ flicker.assertWmStart { isAppWindowInvisible(testApp) }
+ }
+
+ /** Checks that [testApp] window is visible at the end */
+ @Presubmit
+ @Test
+ fun testAppWindowVisibleOnEnd() {
+ flicker.assertWmEnd { isAppWindowVisible(testApp) }
+ }
+
+ /** Checks that [testApp] layer is not visible at the start */
+ @Presubmit
+ @Test
+ fun testAppLayerInvisibleOnStart() {
+ flicker.assertLayersStart { isInvisible(testApp) }
+ }
+
+ /** Checks that [testApp] layer is visible at the end */
+ @Presubmit
+ @Test
+ fun testAppLayerVisibleOnEnd() {
+ flicker.assertLayersEnd { isVisible(testApp) }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] covers the full display area at the start of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun pipAppLayerCoversFullScreenOnStart() {
+ Assume.assumeFalse(tapl.isTablet)
+ flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] covers the full display area at the start of the
+ * transition
+ */
+ @Postsubmit
+ @Test
+ fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
+ Assume.assumeFalse(tapl.isTablet)
+ flicker.assertLayersStart {
+ visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX)).coversExactly(startingBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+ * full display area at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun testAppPlusPipLayerCoversFullScreenOnEnd() {
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(pipApp).region
+ visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 267424412)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
new file mode 100644
index 000000000000..58416660826f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** This test fails because of b/264261596 */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) :
+ EnterPipToOtherOrientation(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
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
deleted file mode 100644
index 7680f4dfa1d5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.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.entireScreenCovered
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test entering pip while changing orientation (from app in landscape to pip window in portrait)
- *
- * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
- *
- * Actions:
- * Launch [testApp] on a fixed portrait orientation
- * Launch [pipApp] on a fixed landscape orientation
- * Broadcast action [ACTION_ENTER_PIP] to enter pip mode
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * 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
-class EnterPipToOtherOrientationTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
- private val testApp = FixedAppHelper(instrumentation)
- private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
-
- /**
- * Defines the transition used to run the test
- */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setupAndTeardown(this)
-
- setup {
- eachRun {
- // Launch a portrait only app on the fullscreen stack
- testApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
- // Launch the PiP activity fixed as landscape
- pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
- }
- }
- teardown {
- eachRun {
- pipApp.exit(wmHelper)
- testApp.exit(wmHelper)
- }
- }
- transitions {
- // Enter PiP, and assert that the PiP is within bounds now that the device is back
- // in portrait
- broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- wmHelper.waitPipShown()
- wmHelper.waitForAppTransitionIdle()
- // during rotation the status bar becomes invisible and reappears at the end
- wmHelper.waitForNavBarStatusBarVisible()
- }
- }
-
- /**
- * Checks that the [FlickerComponentName.NAV_BAR] has the correct position at
- * the start and end of the transition
- */
- @FlakyTest
- @Test
- override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- /**
- * Checks that all parts of the screen are covered at the start and end of the transition
- *
- * TODO b/197726599 Prevents all states from being checked
- */
- @Presubmit
- @Test
- override fun entireScreenCovered() = testSpec.entireScreenCovered(allStates = false)
-
- /**
- * Checks [pipApp] window remains visible and on top throughout the transition
- */
- @Presubmit
- @Test
- fun pipAppWindowIsAlwaysOnTop() {
- testSpec.assertWm {
- isAppWindowOnTop(pipApp.component)
- }
- }
-
- /**
- * Checks that [testApp] window is not visible at the start
- */
- @Presubmit
- @Test
- fun testAppWindowInvisibleOnStart() {
- testSpec.assertWmStart {
- isAppWindowInvisible(testApp.component)
- }
- }
-
- /**
- * Checks that [testApp] window is visible at the end
- */
- @Presubmit
- @Test
- fun testAppWindowVisibleOnEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(testApp.component)
- }
- }
-
- /**
- * Checks that [testApp] layer is not visible at the start
- */
- @Presubmit
- @Test
- fun testAppLayerInvisibleOnStart() {
- testSpec.assertLayersStart {
- isInvisible(testApp.component)
- }
- }
-
- /**
- * Checks that [testApp] layer is visible at the end
- */
- @Presubmit
- @Test
- fun testAppLayerVisibleOnEnd() {
- testSpec.assertLayersEnd {
- isVisible(testApp.component)
- }
- }
-
- /**
- * Checks that the visible region of [pipApp] covers the full display area at the start of
- * the transition
- */
- @Presubmit
- @Test
- fun pipAppLayerCoversFullScreenOnStart() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp.component).coversExactly(startingBounds)
- }
- }
-
- /**
- * Checks that the visible region of [testApp] plus the visible region of [pipApp]
- * cover the full display area at the end of the transition
- */
- @Presubmit
- @Test
- fun testAppPlusPipLayerCoversFullScreenOnEnd() {
- testSpec.assertLayersEnd {
- val pipRegion = visibleRegion(pipApp.component).region
- visibleRegion(testApp.component)
- .plus(pipRegion)
- .coversExactly(endingBounds)
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
new file mode 100644
index 000000000000..e40e5eaad9e2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { pipApp.launchViaIntent(wmHelper) }
+ teardown { pipApp.exit(wmHelper) }
+ }
+
+ /** Checks [pipApp] window remains visible throughout the animation */
+ @Presubmit
+ @Test
+ open fun pipAppWindowAlwaysVisible() {
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
+ }
+
+ /** Checks [pipApp] layer remains visible throughout the animation */
+ @Presubmit
+ @Test
+ open fun pipAppLayerAlwaysVisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ }
+ }
+
+ /** Checks the content overlay appears then disappears during the animation */
+ @Presubmit
+ @Test
+ open fun pipOverlayLayerAppearThenDisappear() {
+ val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+ flicker.assertLayers {
+ this.notContains(overlay)
+ .then()
+ .contains(overlay)
+ .then()
+ .notContains(overlay)
+ }
+ }
+
+ /**
+ * Checks that the pip app window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ fun pipWindowRemainInsideVisibleBounds() {
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ }
+
+ /**
+ * Checks that the pip app layer remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+ coversAtMost(displayBounds)
+ }
+ }
+
+ /** Checks that the visible region of [pipApp] always reduces during the animation */
+ @Presubmit
+ @Test
+ open fun pipLayerReduces() {
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ /** Checks that [pipApp] window becomes pinned */
+ @Presubmit
+ @Test
+ fun pipWindowBecomesPinned() {
+ flicker.assertWm {
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
+ .then()
+ .invoke("pipWindowIsPinned") { it.isPinned(pipApp) }
+ }
+ }
+
+ /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
+ @Presubmit
+ @Test
+ fun launcherLayerBecomesVisible() {
+ flicker.assertLayers {
+ isInvisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isVisible(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ /**
+ * Checks that the focus changes between the [pipApp] window and the launcher when closing the
+ * pip window
+ */
+ @Presubmit
+ @Test
+ open fun focusChanges() {
+ flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
new file mode 100644
index 000000000000..1f060e931be2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Press an "enter pip" button to put [pipApp] in pip mode
+ * ```
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ transitions { pipApp.clickEnterPipButton(wmHelper) }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
new file mode 100644
index 000000000000..4390f0bb70b2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 990872f58dc1..2001f484ed96 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
@@ -17,15 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
+import org.junit.runners.Parameterized
-/**
- * Base class for pip expand tests
- */
-abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- protected val testApp = FixedAppHelper(instrumentation)
+/** Base class for pip expand tests */
+abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
+ protected val testApp = SimpleAppHelper(instrumentation)
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
@@ -34,7 +36,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
+ flicker.assertWmVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
coversAtMost(displayBounds)
}
}
@@ -46,7 +48,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
coversAtMost(displayBounds)
}
}
@@ -58,15 +60,15 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun showBothAppWindowsThenHidePip() {
- testSpec.assertWm {
+ flicker.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
- isAppWindowVisible(testApp.component)
- .isAppWindowOnTop(pipApp.component)
- .then()
- .isAppWindowInvisible(testApp.component)
- .isAppWindowVisible(pipApp.component)
+ isAppWindowVisible(testApp)
+ .isAppWindowOnTop(pipApp)
+ .then()
+ .isAppWindowInvisible(testApp)
+ .isAppWindowVisible(pipApp)
}
}
@@ -77,54 +79,66 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans
@Presubmit
@Test
open fun showBothAppLayersThenHidePip() {
- testSpec.assertLayers {
- isVisible(testApp.component)
- .isVisible(pipApp.component)
- .then()
- .isInvisible(testApp.component)
- .isVisible(pipApp.component)
+ flicker.assertLayers {
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ .then()
+ .isInvisible(testApp)
+ .isVisible(pipApp)
}
}
/**
- * Checks that the visible region of [testApp] plus the visible region of [pipApp]
- * cover the full display area at the start of the transition
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+ * full display area at the start of the transition
*/
@Presubmit
@Test
open fun testPlusPipAppsCoverFullScreenAtStart() {
- testSpec.assertLayersStart {
- val pipRegion = visibleRegion(pipApp.component).region
- visibleRegion(testApp.component)
- .plus(pipRegion)
- .coversExactly(displayBounds)
+ flicker.assertLayersStart {
+ val pipRegion = visibleRegion(pipApp).region
+ visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
}
}
/**
- * Checks that the visible region oft [pipApp] covers the full display area at the end of
- * the transition
+ * Checks that the visible region oft [pipApp] covers the full display area at the end of the
+ * transition
*/
@Presubmit
@Test
open fun pipAppCoversFullScreenAtEnd() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp.component).coversExactly(displayBounds)
- }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
}
- /**
- * Checks that the visible region of [pipApp] always expands during the animation
- */
+ /** Checks that the visible region of [pipApp] always expands during the animation */
@Presubmit
@Test
open fun pipLayerExpands() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
}
}
+
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 0768e82e491c..313631cbe8ee 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/ExitPipToAppViaExpandButtonTest.kt
@@ -16,14 +16,14 @@
package com.android.wm.shell.flicker.pip
-import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import 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 org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,65 +36,55 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-@FlakyTest(bugId = 219750830)
-class ExitPipViaExpandButtonClickTest(
- testSpec: FlickerTestParameter
-) : ExitPipToAppTransition(testSpec) {
+open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = true) {
+ get() = buildTransition {
setup {
- eachRun {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
}
transitions {
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
@Test
- override fun pipLayerExpands() = super.pipLayerExpands()
+ override fun pipLayerExpands() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ super.pipLayerExpands()
+ }
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
- }
+ @Presubmit
+ @Test
+ fun pipLayerExpands_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ super.pipLayerExpands()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
new file mode 100644
index 000000000000..eccb85d98798
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) :
+ ExitPipToAppViaExpandButtonTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index c6a705dacb8d..93ffdd8d5294 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/ExitPipToAppViaIntentTest.kt
@@ -16,16 +16,13 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import 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.helpers.isShellTransitionsEnabled
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -39,60 +36,61 @@ import org.junit.runners.Parameterized
* To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen via an intent
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = true) {
+ get() = buildTransition {
setup {
- eachRun {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
}
transitions {
// This will bring PipApp to fullscreen
pipApp.exitPipToFullScreenViaIntent(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun statusBarLayerRotatesScales() {
+ override fun statusBarLayerPositionAtStartAndEnd() {
Assume.assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
+ super.statusBarLayerPositionAtStartAndEnd()
}
@Presubmit
@Test
fun statusBarLayerRotatesScales_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- super.statusBarLayerRotatesScales()
+ super.statusBarLayerPositionAtStartAndEnd()
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
@Test
override fun pipLayerExpands() {
@@ -106,19 +104,4 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
Assume.assumeTrue(isShellTransitionsEnabled)
super.pipLayerExpands()
}
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
new file mode 100644
index 000000000000..6ab6a1f0bb73
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
deleted file mode 100644
index 0b4bc761838d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-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 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.() -> Unit
- get() = buildTransition(eachRun = true) {
- setup {
- eachRun {
- this.setRotation(testSpec.startRotation)
- }
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- /**
- * Checks that [pipApp] window is pinned and visible at the start and then becomes
- * unpinned and invisible at the same moment, and remains unpinned and invisible
- * until the end of the transition
- */
- @Presubmit
- @Test
- open fun pipWindowBecomesInvisible() {
- if (isShellTransitionsEnabled) {
- // When Shell transition is enabled, we change the windowing mode at start, but
- // update the visibility after the transition is finished, so we can't check isNotPinned
- // and isAppWindowInvisible in the same assertion block.
- 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)
- }
- }
- }
- }
-
- /**
- * Checks that [pipApp] and [LAUNCHER_COMPONENT] layers are visible at the start
- * of the transition. Then [pipApp] layer becomes invisible, and remains invisible
- * until the end of the transition
- */
- @Presubmit
- @Test
- open fun pipLayerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(pipApp.component)
- .isVisible(LAUNCHER_COMPONENT)
- .then()
- .isInvisible(pipApp.component)
- .isVisible(LAUNCHER_COMPONENT)
- }
- }
-}
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 28b7fc9bd29e..7d5f740838bd 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,16 +16,14 @@
package com.android.wm.shell.flicker.pip
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
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.LAUNCHER_COMPONENT
-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
@@ -33,34 +31,32 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test expanding a pip window by double clicking it
+ * Test expanding a pip window by double-clicking it
*
* To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = true) {
- transitions {
- pipApp.doubleClickPipWindow(wmHelper)
- }
- }
+ get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
@@ -69,9 +65,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -81,42 +75,29 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
- /**
- * Checks [pipApp] window remains visible throughout the animation
- */
+ /** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
@Test
fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm {
- isAppWindowVisible(pipApp.component)
- }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(pipApp.component)
- }
+ flicker.assertLayers { isVisible(pipApp) }
}
- /**
- * Checks that the visible region of [pipApp] always expands during the animation
- */
- @FlakyTest(bugId = 228012337)
+ /** Checks that the visible region of [pipApp] always expands during the animation */
+ @Presubmit
@Test
fun pipLayerExpands() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
@@ -126,65 +107,48 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Presubmit
@Test
fun pipSameAspectRatio() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.isSameAspectRatio(previous.visibleRegion)
}
}
}
- /**
- * Checks [pipApp] window remains pinned throughout the animation
- */
+ /** Checks [pipApp] window remains pinned throughout the animation */
@Presubmit
@Test
fun windowIsAlwaysPinned() {
- testSpec.assertWm {
- this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
- }
+ flicker.assertWm { this.invoke("hasPipWindow") { it.isPinned(pipApp) } }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
@Presubmit
@Test
fun launcherIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(LAUNCHER_COMPONENT)
- }
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LAUNCHER) }
}
- /**
- * Checks that the focus doesn't change between windows during the transition
- */
- @FlakyTest(bugId = 216306753)
+ /** Checks that the focus doesn't change between windows during the transition */
+ @Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
new file mode 100644
index 000000000000..c09623490041
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) :
+ ExpandPipOnDoubleClickTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
new file mode 100644
index 000000000000..0b73aac02797
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test expanding a pip window via pinch out gesture. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
+
+ /** Checks that the visible region area of [pipApp] always increases during the animation. */
+ @Presubmit
+ @Test
+ fun pipLayerAreaIncreases() {
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
new file mode 100644
index 000000000000..e064bf2ee921
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
new file mode 100644
index 000000000000..9c007449fb8d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.Direction
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip movement with Launcher shelf height change (increase).
+ *
+ * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ *
+ * Actions:
+ * ```
+ * Launch [pipApp] in pip mode
+ * Press home
+ * Launch [testApp]
+ * 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
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
+ teardown {
+ tapl.pressHome()
+ testApp.exit(wmHelper)
+ }
+ transitions { testApp.launchViaIntent(wmHelper) }
+ }
+
+ /** Checks that the visible region of [pipApp] window always moves down during the animation. */
+ @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+
+ /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
+ @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
+}
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
deleted file mode 100644
index 8729bb6776f0..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.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.region.RegionSubject
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip 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
-open class MovePipDownShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
- /**
- * Defines the transition used to run the test
- */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = false) {
- teardown {
- eachRun {
- testApp.launchViaIntent(wmHelper)
- }
- test {
- testApp.exit(wmHelper)
- }
- }
- transitions {
- taplInstrumentation.pressHome()
- }
- }
-
- override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
- current.isHigherOrEqual(previous.region)
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- 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/MovePipOnImeVisibilityChangeTest.kt
index 1e30f6b83874..c23838a987bf 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/MovePipOnImeVisibilityChangeTest.kt
@@ -17,19 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
-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
@@ -38,17 +36,12 @@ import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
- */
+/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-@FlakyTest(bugId = 218604389)
-open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
@Before
@@ -56,20 +49,14 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
assumeFalse(isShellTransitionsEnabled)
}
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = false) {
+ get() = buildTransition {
setup {
- test {
- imeApp.launchViaIntent(wmHelper)
- setRotation(testSpec.startRotation)
- }
- }
- teardown {
- test {
- imeApp.exit(wmHelper)
- setRotation(Surface.ROTATION_0)
- }
+ imeApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
}
+ teardown { imeApp.exit(wmHelper) }
transitions {
// open the soft keyboard
imeApp.openIME(wmHelper)
@@ -80,32 +67,21 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- /**
- * Ensure the pip window remains visible throughout any keyboard interactions
- */
+ /** Ensure the pip window remains visible throughout any keyboard interactions */
@Presubmit
@Test
open fun pipInVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ flicker.assertWmVisibleRegion(pipApp) {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
coversAtMost(displayBounds)
}
}
- /**
- * Ensure that the pip window does not obscure the keyboard
- */
+ /** Ensure that the pip window does not obscure the keyboard */
@Presubmit
@Test
open fun pipIsAboveAppWindow() {
- testSpec.assertWmTag(TAG_IME_VISIBLE) {
- isAboveWindow(FlickerComponentName.IME, pipApp.component)
- }
+ flicker.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
}
companion object {
@@ -113,10 +89,10 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
new file mode 100644
index 000000000000..d3d77d20662e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) :
+ MovePipOnImeVisibilityChangeTest(flicker) {
+ companion object {
+ private const val TAG_IME_VISIBLE = "imeIsVisible"
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
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/MovePipOnImeVisibilityChangeTestShellTransit.kt
index fe51228230cb..6f8111690f0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
@@ -17,11 +17,10 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import 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
@@ -34,8 +33,8 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+class MovePipOnImeVisibilityChangeTestShellTransit(flicker: FlickerTest) :
+ MovePipOnImeVisibilityChangeTest(flicker) {
@Before
override fun before() {
@@ -44,5 +43,5 @@ class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardT
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-} \ No newline at end of file
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+}
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 0499e7de9a0a..109354ab5c79 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
@@ -17,46 +17,31 @@
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.region.RegionSubject
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import android.tools.common.Rotation
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.wm.shell.flicker.Direction
import org.junit.Test
+import org.junit.runners.Parameterized
-/**
- * Base class for pip tests with Launcher shelf height change
- */
-abstract class MovePipShelfHeightTransition(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
- protected val taplInstrumentation = LauncherInstrumentation()
- protected val testApp = FixedAppHelper(instrumentation)
-
- /**
- * Checks if the window movement direction is valid
- */
- protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject)
+/** Base class for pip tests with Launcher shelf height change */
+abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
+ protected val testApp = FixedOrientationAppHelper(instrumentation)
- /**
- * Checks [pipApp] window remains visible throughout the animation
- */
+ /** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
@Test
open fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm {
- isAppWindowVisible(pipApp.component)
- }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
open fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(pipApp.component)
- }
+ flicker.assertLayers { isVisible(pipApp) }
}
/**
@@ -66,9 +51,7 @@ abstract class MovePipShelfHeightTransition(
@Presubmit
@Test
open fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -78,39 +61,65 @@ abstract class MovePipShelfHeightTransition(
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
- * Checks that the visible region of [pipApp] always moves in the correct direction
+ * Checks that the visible region of [pipApp] window always moves in the specified direction
* during the animation.
*/
- @Presubmit
- @Test
- open fun pipWindowMoves() {
- val windowName = pipApp.component.toWindowName()
- testSpec.assertWm {
- val pipWindowList = this.windowStates { it.name.contains(windowName) && it.isVisible }
- pipWindowList.zipWithNext { previous, current ->
- assertRegionMovement(previous.frame, current.frame)
+ protected fun pipWindowMoves(direction: Direction) {
+ flicker.assertWm {
+ val pipWindowFrameList =
+ this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame }
+ when (direction) {
+ Direction.UP -> assertRegionMovementUp(pipWindowFrameList)
+ Direction.DOWN -> assertRegionMovementDown(pipWindowFrameList)
+ else -> error("Unhandled direction")
}
}
}
/**
- * Checks that the visible region of [pipApp] always moves up during the animation
+ * Checks that the visible region of [pipApp] layer always moves in the specified direction
+ * during the animation.
*/
- @Presubmit
- @Test
- open fun pipLayerMoves() {
- val layerName = pipApp.component.toLayerName()
- testSpec.assertLayers {
- val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
- pipLayerList.zipWithNext { previous, current ->
- assertRegionMovement(previous.visibleRegion, current.visibleRegion)
+ protected fun pipLayerMoves(direction: Direction) {
+ flicker.assertLayers {
+ val pipLayerRegionList =
+ this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ .map { it.visibleRegion }
+ when (direction) {
+ Direction.UP -> assertRegionMovementUp(pipLayerRegionList)
+ Direction.DOWN -> assertRegionMovementDown(pipLayerRegionList)
+ else -> error("Unhandled direction")
}
}
}
-} \ No newline at end of file
+
+ private fun assertRegionMovementDown(regions: List<RegionSubject>) {
+ regions.zipWithNext { previous, current -> current.isLowerOrEqual(previous) }
+ regions.last().isLower(regions.first())
+ }
+
+ private fun assertRegionMovementUp(regions: List<RegionSubject>) {
+ regions.zipWithNext { previous, current -> current.isHigherOrEqual(previous.region) }
+ regions.last().isHigher(regions.first())
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
new file mode 100644
index 000000000000..c8d5624b1d77
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.Direction
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip 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 up (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
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() =
+ buildTransition() {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
+ }
+
+ /** Checks that the visible region of [pipApp] window always moves up during the animation. */
+ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+
+ /** Checks that the visible region of [pipApp] layer always moves up during the animation. */
+ @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
+}
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
deleted file mode 100644
index 388b5e0b5e47..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-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.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
-
-/**
- * Test Pip movement with Launcher shelf height change (increase).
- *
- * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
- *
- * Actions:
- * Launch [pipApp] in pip mode
- * Press home
- * Launch [testApp]
- * Check if pip window moves up (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
-class MovePipUpShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
- @Before
- fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
- /**
- * Defines the transition used to run the test
- */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = false) {
- teardown {
- eachRun {
- taplInstrumentation.pressHome()
- }
- test {
- testApp.exit(wmHelper)
- }
- }
- transitions {
- testApp.launchViaIntent(wmHelper)
- }
- }
-
- override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
- current.isLowerOrEqual(previous.region)
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
new file mode 100644
index 000000000000..53ce3936fbe4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.graphics.Rect
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the snapping of a PIP window via dragging, releasing, and checking its final location.
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){
+ // represents the direction in which the pip window should be snapping
+ private var willSnapRight: Boolean = true
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ val stringExtras: Map<String, String> =
+ mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+
+ // cache the starting bounds here
+ val startBounds = Rect()
+
+ setup {
+ // Launch the PIP activity and wait for it to enter PiP mode
+ setRotation(Rotation.ROTATION_0)
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+
+ // get the initial region bounds and cache them
+ val initRegion = pipApp.getWindowRect(wmHelper)
+ startBounds
+ .set(initRegion.left, initRegion.top, initRegion.right, initRegion.bottom)
+
+ // drag the pip window away from the edge
+ pipApp.dragPipWindowAwayFromEdge(wmHelper, 50)
+
+ // determine the direction in which the snapping should occur
+ willSnapRight = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ transitions {
+ // continue the transition until the PIP snaps
+ pipApp.waitForPipToSnapTo(wmHelper, startBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region area of [pipApp] moves to closest edge during the animation.
+ */
+ @Postsubmit
+ @Test
+ fun pipLayerMovesToClosestEdge() {
+ flicker.assertLayers {
+ val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ if (willSnapRight) {
+ current.visibleRegion.isToTheRight(previous.visibleRegion.region)
+ } else {
+ previous.visibleRegion.isToTheRight(current.visibleRegion.region)
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
new file mode 100644
index 000000000000..85b2fbce2f21
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test minimizing a pip window via pinch in gesture. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+
+ /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+ @Postsubmit
+ @Test
+ fun pipLayerAreaDecreases() {
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
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
deleted file mode 100644
index 9fad4997e63a..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip Stack in bounds after rotations.
- *
- * To run this test: `atest WMShellFlickerTests:PipRotationTest`
- *
- * Actions:
- * Launch a [pipApp] in pip mode
- * Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
- * (usually, 0->90 and 90->0)
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited from [PipTransition]
- * 2. Part of the test setup occurs automatically via
- * [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)
-@Group4
-open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- private val fixedApp = FixedAppHelper(instrumentation)
- private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
- private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
-
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition(eachRun = false) {
- setup {
- test {
- fixedApp.launchViaIntent(wmHelper)
- }
- eachRun {
- setRotation(testSpec.startRotation)
- }
- }
- transitions {
- setRotation(testSpec.endRotation)
- }
- }
-
- /**
- * Checks that all parts of the screen are covered at the start and end of the transition
- */
- @Presubmit
- @Test
- override fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- /**
- * Checks the position of the navigation bar at the start and end of the transition
- */
- @FlakyTest
- @Test
- override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- /**
- * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
- */
- @Presubmit
- @Test
- fun appLayerRotates_StartingBounds() {
- testSpec.assertLayersStart {
- visibleRegion(fixedApp.component).coversExactly(screenBoundsStart)
- }
- }
-
- /**
- * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
- */
- @Presubmit
- @Test
- fun appLayerRotates_EndingBounds() {
- testSpec.assertLayersEnd {
- visibleRegion(fixedApp.component).coversExactly(screenBoundsEnd)
- }
- }
-
- /**
- * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
- */
- private fun pipLayerRotates_StartingBounds_internal() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
- }
- }
-
- /**
- * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
- */
- @Presubmit
- @Test
- fun pipLayerRotates_StartingBounds() {
- pipLayerRotates_StartingBounds_internal()
- }
-
- /**
- * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
- */
- @Presubmit
- @Test
- fun pipLayerRotates_EndingBounds() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp.component).coversAtMost(screenBoundsEnd)
- }
- }
-
- /**
- * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
- * transition
- */
- @Presubmit
- @Test
- fun pipIsAboveFixedAppWindow_Start() {
- testSpec.assertWmStart {
- isAboveWindow(pipApp.component, fixedApp.component)
- }
- }
-
- /**
- * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
- * transition
- */
- @Presubmit
- @Test
- fun pipIsAboveFixedAppWindow_End() {
- testSpec.assertWmEnd {
- isAboveWindow(pipApp.component, fixedApp.component)
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- repetitions = 3)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
deleted file mode 100644
index 7ba085d3cf1a..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import com.android.wm.shell.flicker.FlickerTestBase
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import org.junit.Before
-
-abstract class PipTestBase(
- rotationName: String,
- rotation: Int
-) : FlickerTestBase(rotationName, rotation) {
- protected val testApp = PipAppHelper(instrumentation)
-
- @Before
- override fun televisionSetUp() {
- /**
- * The super implementation assumes ([org.junit.Assume]) that not running on TV, thus
- * disabling the test on TV. This test, however, *should run on TV*, so we overriding this
- * method and simply leaving it blank.
- */
- }
-}
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 8d542c8ec9e6..b30f30830156 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
@@ -19,32 +19,24 @@ package com.android.wm.shell.flicker.pip
import android.app.Instrumentation
import android.content.Intent
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-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.entireScreenCovered
-import com.android.server.wm.flicker.helpers.WindowUtils
+import android.tools.common.Rotation
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import android.tools.device.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.PipAppHelper
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.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.testapp.Components
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import com.google.common.truth.Truth
import org.junit.Test
-abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected val pipApp = PipAppHelper(instrumentation)
- protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
- 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 {
@@ -52,93 +44,37 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
}
fun doAction(broadcastAction: String) {
- instrumentation.context
- .sendBroadcast(createIntentWithAction(broadcastAction))
+ instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction))
}
companion object {
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- @JvmStatic
- val ORIENTATION_LANDSCAPE = 0
+ @JvmStatic val ORIENTATION_LANDSCAPE = 0
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- @JvmStatic
- val ORIENTATION_PORTRAIT = 1
+ @JvmStatic val ORIENTATION_PORTRAIT = 1
}
}
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- /**
- * Gets a configuration that handles basic setup and teardown of pip tests
- */
- protected val setupAndTeardown: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- }
- }
- teardown {
- eachRun {
- setRotation(Surface.ROTATION_0)
- }
- test {
- removeAllTasksButHome()
- pipApp.exit(wmHelper)
- }
- }
- }
-
/**
- * Gets a configuration that handles basic setup and teardown of pip tests and that
- * launches the Pip app for test
+ * Gets a configuration that handles basic setup and teardown of pip tests and that launches the
+ * Pip app for test
*
- * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
* @param stringExtras Arguments to pass to the PIP launch intent
- * @param extraSpec Addicional segment of flicker specification
+ * @param extraSpec Additional segment of flicker specification
*/
@JvmOverloads
protected open fun buildTransition(
- eachRun: Boolean,
- stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+ stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
extraSpec: FlickerBuilder.() -> Unit = {}
): FlickerBuilder.() -> Unit {
return {
- setupAndTeardown(this)
-
setup {
- test {
- if (!eachRun) {
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipShown()
- }
- }
- eachRun {
- if (eachRun) {
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipShown()
- }
- }
- }
- teardown {
- eachRun {
- if (eachRun) {
- pipApp.exit(wmHelper)
- }
- }
- test {
- if (!eachRun) {
- pipApp.exit(wmHelper)
- }
- }
+ setRotation(Rotation.ROTATION_0)
+ removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
}
+ teardown { pipApp.exit(wmHelper) }
extraSpec(this)
}
@@ -146,29 +82,17 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- open fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- open fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- open fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- open fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @Presubmit
- @Test
- open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @Presubmit
- @Test
- open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+ fun hasAtMostOnePipDismissOverlayWindow() {
+ val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
+ flicker.assertWm {
+ val overlaysPerState =
+ trace.entries.map { entry ->
+ entry.windowStates.count { window -> matcher.windowMatchesAnyOf(window) } <= 1
+ }
- @Presubmit
- @Test
- open fun entireScreenCovered() = testSpec.entireScreenCovered()
-} \ No newline at end of file
+ Truth.assertWithMessage("Number of dismiss overlays per state")
+ .that(overlaysPerState)
+ .doesNotContain(false)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
new file mode 100644
index 000000000000..3850c1f6c89a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.app.Activity
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test exiting Pip with orientation changes. To run this test: `atest
+ * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) {
+ private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+ private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ // Launch the PiP activity fixed as landscape.
+ pipApp.launchViaIntent(
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
+ // Enter PiP.
+ broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
+ // System bar may fade out during fixed rotation.
+ wmHelper
+ .StateSyncBuilder()
+ .withPipShown()
+ .withRotation(Rotation.ROTATION_0)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ teardown { pipApp.exit(wmHelper) }
+ transitions {
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
+ pipApp.launchViaIntent(wmHelper)
+ // System bar may fade out during fixed rotation.
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(pipApp)
+ .withRotation(Rotation.ROTATION_90)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ /**
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ */
+ @Before
+ fun setup() {
+ Assume.assumeFalse(tapl.isTablet)
+ }
+
+ @Presubmit
+ @Test
+ fun displayEndsAt90Degrees() {
+ flicker.assertWmEnd { hasRotation(Rotation.ROTATION_90) }
+ }
+
+ @Presubmit
+ @Test
+ fun pipWindowInsideDisplay() {
+ flicker.assertWmStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppShowsOnTop() {
+ flicker.assertWmEnd { isAppWindowOnTop(pipApp) }
+ }
+
+ @Presubmit
+ @Test
+ fun pipLayerInsideDisplay() {
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAlwaysVisible() {
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppLayerCoversFullScreen() {
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) }
+ }
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 264243884)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+}
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
deleted file mode 100644
index 51339a1deb4b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-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 org.junit.FixMethodOrder
-import org.junit.Test
-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:SetRequestedOrientationWhilePinnedTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-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.() -> Unit
- get() = {
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- // Launch the PiP activity fixed as landscape.
- pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- 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 {
- // 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)
- wmHelper.waitForAppTransitionIdle()
- // System bar may fade out during fixed rotation.
- wmHelper.waitForNavBarStatusBarVisible()
- }
- }
-
- @Presubmit
- @Test
- fun displayEndsAt90Degrees() {
- testSpec.assertWmEnd {
- hasRotation(Surface.ROTATION_90)
- }
- }
-
- @Presubmit
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @FlakyTest
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Presubmit
- @Test
- fun pipWindowInsideDisplay() {
- testSpec.assertWmStart {
- frameRegion(pipApp.component).coversAtMost(startingBounds)
- }
- }
-
- @Presubmit
- @Test
- fun pipAppShowsOnTop() {
- testSpec.assertWmEnd {
- isAppWindowOnTop(pipApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun pipLayerInsideDisplay() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp.component).coversAtMost(startingBounds)
- }
- }
-
- @Presubmit
- @Test
- fun pipAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(pipApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun pipAppLayerCoversFullScreen() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp.component).coversExactly(endingBounds)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 1)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
new file mode 100644
index 000000000000..2cf8f61f13fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip Stack in bounds after rotations.
+ *
+ * To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ *
+ * Actions:
+ * ```
+ * Launch a [pipApp] in pip mode
+ * Launch another app [fixedApp] (appears below pip)
+ * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
+ * (usually, 0->90 and 90->0)
+ * ```
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) {
+ private val testApp = SimpleAppHelper(instrumentation)
+ private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+ private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition {
+ setup {
+ testApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
+ }
+ transitions { setRotation(flicker.scenario.endRotation) }
+ }
+
+ /** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */
+ @Presubmit
+ @Test
+ fun fixedAppLayer_StartingBounds() {
+ flicker.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
+ }
+
+ /** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */
+ @Presubmit
+ @Test
+ fun fixedAppLayer_EndingBounds() {
+ flicker.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
+ }
+
+ /**
+ * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun appLayers_StartingBounds() {
+ flicker.assertLayersStart {
+ visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
+ }
+ }
+
+ /**
+ * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun appLayers_EndingBounds() {
+ flicker.assertLayersEnd { visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) }
+ }
+
+ /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
+ private fun pipLayerRotates_StartingBounds_internal() {
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
+ }
+
+ /** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_StartingBounds() {
+ pipLayerRotates_StartingBounds_internal()
+ }
+
+ /** Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_EndingBounds() {
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
+ }
+
+ /**
+ * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipIsAboveFixedAppWindow_Start() {
+ flicker.assertWmStart { isAboveWindow(pipApp, testApp) }
+ }
+
+ /**
+ * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipIsAboveFixedAppWindow_End() {
+ flicker.assertWmEnd { isAboveWindow(pipApp, testApp) }
+ }
+
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
new file mode 100644
index 000000000000..b7a2c47e3b32
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
new file mode 100644
index 000000000000..000ae8f9458e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.tv
+
+import android.app.Instrumentation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.PipAppHelper
+
+/** Helper class for PIP app on AndroidTV */
+open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
+ private val appSelector = By.pkg(`package`).depth(0)
+
+ val ui: UiObject2?
+ get() = uiDevice.findObject(appSelector)
+
+ 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"
+ // from "the bottom".
+ repeat(FOCUS_ATTEMPTS) {
+ uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
+ ?: error("The object we try to focus on is gone.")
+
+ uiDevice.pressDPadDown()
+ uiDevice.waitForIdle()
+ }
+ return false
+ }
+
+ override fun clickObject(resId: String) {
+ val selector = By.res(`package`, resId)
+ focusOnObject(selector) || error("Could not focus on `$resId` object")
+ uiDevice.pressDPadCenter()
+ }
+
+ @Deprecated(
+ "Use PipAppHelper.closePipWindow(wmHelper) instead",
+ ReplaceWith("closePipWindow(wmHelper)")
+ )
+ override fun closePipWindow() {
+ uiDevice.closeTvPipWindow()
+ }
+
+ /** Taps the pip window and dismisses it by clicking on the X button. */
+ override fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+ uiDevice.closeTvPipWindow()
+
+ // Wait for animation to complete.
+ wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ fun waitUntilClosed(): Boolean {
+ val appSelector = By.pkg(`package`).depth(0)
+ return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
+ }
+
+ companion object {
+ private const val FOCUS_ATTEMPTS = 20
+ private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 9c50630095be..2cb18f948f0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,47 +14,36 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker
+package com.android.wm.shell.flicker.pip.tv
import android.app.Instrumentation
import android.content.pm.PackageManager
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.runners.Parameterized
-/**
- * Base class of all Flicker test that performs common functions for all flicker tests:
- *
- * - Caches transitions so that a transition is run once and the transition results are used by
- * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
- * multiple times.
- * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
- * - Fails tests if results are not available for any test due to jank.
- */
-abstract class FlickerTestBase(
- protected val rotationName: String,
- protected val rotation: Int
-) {
+abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val packageManager: PackageManager = instrumentation.context.packageManager
protected val isTelevision: Boolean by lazy {
packageManager.run {
- hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
+ hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
+ hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
}
}
+ protected val testApp = PipAppHelperTv(instrumentation)
- /**
- * By default WmShellFlickerTests do not run on TV devices.
- * If the test should run on TV - it should override this method.
- */
@Before
- open fun televisionSetUp() = assumeFalse(isTelevision)
+ open fun televisionSetUp() {
+ /**
+ * The super implementation assumes ([org.junit.Assume]) that not running on TV, thus
+ * disabling the test on TV. This test, however, *should run on TV*, so we overriding this
+ * method and simply leaving it blank.
+ */
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 49094e609fbc..8a073abf032c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
@@ -25,16 +25,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-/**
- * Test Pip Menu on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipBasicTest`
- */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */
@RequiresDevice
@RunWith(Parameterized::class)
-class TvPipBasicTest(
- private val radioButtonId: String,
- private val pipWindowRatio: Rational?
-) : TvPipTestBase() {
+class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) :
+ TvPipTestBase() {
@Test
fun enterPip_openMenu_pressBack_closePip() {
@@ -43,10 +38,10 @@ class TvPipBasicTest(
// Set up ratio and enter Pip
testApp.clickObject(radioButtonId)
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
- val actualRatio: Float = testApp.ui?.visibleBounds?.ratio
- ?: fail("Application UI not found")
+ val actualRatio: Float =
+ testApp.ui?.visibleBounds?.ratio ?: fail("Application UI not found")
pipWindowRatio?.let { expectedRatio ->
assertEquals("Wrong Pip window ratio", expectedRatio.toFloat(), actualRatio)
}
@@ -62,7 +57,8 @@ class TvPipBasicTest(
// Make sure Pip Window ration remained the same after Pip menu was closed
testApp.ui?.visibleBounds?.let { newBounds ->
assertEquals("Pip window ratio has changed", actualRatio, newBounds.ratio)
- } ?: fail("Application UI not found")
+ }
+ ?: fail("Application UI not found")
// Close Pip
testApp.closePipWindow()
@@ -77,11 +73,11 @@ class TvPipBasicTest(
fun getParams(): Collection<Array<Any?>> {
infix fun Int.to(denominator: Int) = Rational(this, denominator)
return listOf(
- arrayOf("ratio_default", null),
- arrayOf("ratio_square", 1 to 1),
- arrayOf("ratio_wide", 2 to 1),
- arrayOf("ratio_tall", 1 to 2)
+ arrayOf("ratio_default", null),
+ arrayOf("ratio_square", 1 to 1),
+ arrayOf("ratio_wide", 2 to 1),
+ arrayOf("ratio_tall", 1 to 2)
)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 061218a015e4..0432a8497fbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -19,35 +19,37 @@ package com.android.wm.shell.flicker.pip.tv
import android.graphics.Rect
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.UiObject2
+import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
import com.android.wm.shell.flicker.wait
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
-/**
- * Test Pip Menu on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipMenuTests`
- */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */
@RequiresDevice
class TvPipMenuTests : TvPipTestBase() {
- private val systemUiResources =
- packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
- private val pipBoundsWhileInMenu: Rect = systemUiResources.run {
- val bounds = getString(getIdentifier("pip_menu_bounds", "string",
- SYSTEM_UI_PACKAGE_NAME))
- Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds")
+ private val systemUiResources by lazy {
+ packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ }
+ private val pipBoundsWhileInMenu: Rect by lazy {
+ systemUiResources.run {
+ val bounds =
+ getString(getIdentifier("pip_menu_bounds", "string", SYSTEM_UI_PACKAGE_NAME))
+ Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds")
+ }
}
- private val playButtonDescription = systemUiResources.run {
- getString(getIdentifier("pip_play", "string",
- SYSTEM_UI_PACKAGE_NAME))
+ private val playButtonDescription by lazy {
+ systemUiResources.run {
+ getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME))
+ }
}
- private val pauseButtonDescription = systemUiResources.run {
- getString(getIdentifier("pip_pause", "string",
- SYSTEM_UI_PACKAGE_NAME))
+ private val pauseButtonDescription by lazy {
+ systemUiResources.run {
+ getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME))
+ }
}
@Before
@@ -61,20 +63,29 @@ class TvPipMenuTests : TvPipTestBase() {
enterPip_openMenu_assertShown()
// Make sure the PiP task is positioned where it should be.
- val activityBounds: Rect = testApp.ui?.visibleBounds
- ?: error("Could not retrieve Pip Activity bounds")
- assertTrue("Pip Activity is positioned correctly while Pip menu is shown",
- pipBoundsWhileInMenu == activityBounds)
+ val activityBounds: Rect =
+ testApp.ui?.visibleBounds ?: error("Could not retrieve Pip Activity bounds")
+ assertTrue(
+ "Pip Activity is positioned correctly while Pip menu is shown",
+ pipBoundsWhileInMenu == activityBounds
+ )
// Make sure the Pip Menu Actions are positioned correctly.
uiDevice.findTvPipMenuControls()?.visibleBounds?.run {
- assertTrue("Pip Menu Actions should be positioned below the Activity in Pip",
- top >= activityBounds.bottom)
- assertTrue("Pip Menu Actions should be positioned central horizontally",
- centerX() == uiDevice.displayWidth / 2)
- assertTrue("Pip Menu Actions should be fully shown on the screen",
- left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight)
- } ?: error("Could not retrieve Pip Menu Actions bounds")
+ assertTrue(
+ "Pip Menu Actions should be positioned below the Activity in Pip",
+ top >= activityBounds.bottom
+ )
+ assertTrue(
+ "Pip Menu Actions should be positioned central horizontally",
+ centerX() == uiDevice.displayWidth / 2
+ )
+ assertTrue(
+ "Pip Menu Actions should be fully shown on the screen",
+ left >= 0 && right <= uiDevice.displayWidth && bottom <= uiDevice.displayHeight
+ )
+ }
+ ?: error("Could not retrieve Pip Menu Actions bounds")
testApp.closePipWindow()
}
@@ -107,7 +118,7 @@ class TvPipMenuTests : TvPipTestBase() {
// PiP menu should contain the Close button
uiDevice.findTvPipMenuCloseButton()
- ?: fail("\"Close PIP\" button should be shown in Pip menu")
+ ?: fail("\"Close PIP\" button should be shown in Pip menu")
// Clicking on the Close button should close the app
uiDevice.clickTvPipMenuCloseButton()
@@ -120,13 +131,15 @@ class TvPipMenuTests : TvPipTestBase() {
// PiP menu should contain the Fullscreen button
uiDevice.findTvPipMenuFullscreenButton()
- ?: fail("\"Full screen\" button should be shown in Pip menu")
+ ?: fail("\"Full screen\" button should be shown in Pip menu")
// Clicking on the fullscreen button should return app to the fullscreen mode.
// Click, wait for the app to go fullscreen
uiDevice.clickTvPipMenuFullscreenButton()
- assertTrue("\"Full screen\" button should open the app fullscreen",
- wait { testApp.ui?.isFullscreen(uiDevice) ?: false })
+ assertTrue(
+ "\"Full screen\" button should open the app fullscreen",
+ wait { testApp.ui?.isFullscreen(uiDevice) ?: false }
+ )
// Close the app
uiDevice.pressBack()
@@ -143,8 +156,10 @@ class TvPipMenuTests : TvPipTestBase() {
// PiP menu should contain the Pause button
uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
- ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
- "playing media session.")
+ ?: fail(
+ "\"Pause\" button should be shown in Pip menu if there is an active " +
+ "playing media session."
+ )
// When we pause media, the button should change from Pause to Play
uiDevice.clickTvPipMenuElementWithDescription(pauseButtonDescription)
@@ -152,8 +167,10 @@ class TvPipMenuTests : TvPipTestBase() {
assertFullscreenAndCloseButtonsAreShown()
// PiP menu should contain the Play button now
uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription)
- ?: fail("\"Play\" button should be shown in Pip menu if there is an active " +
- "paused media session.")
+ ?: fail(
+ "\"Play\" button should be shown in Pip menu if there is an active " +
+ "paused media session."
+ )
testApp.closePipWindow()
}
@@ -165,44 +182,47 @@ class TvPipMenuTests : TvPipTestBase() {
enterPip_openMenu_assertShown()
// PiP menu should contain "No-Op", "Off" and "Clear" buttons...
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
- ?: fail("\"No-Op\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
- ?: fail("\"Off\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
- ?: fail("\"Clear\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+ ?: fail("\"No-Op\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+ ?: fail("\"Off\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
// ... and should also contain the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
// Invoking the "Off" action should replace it with the "On" action/button and should
// remove the "No-Op" action/button. "Clear" action/button should remain in the menu ...
- uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON)
- ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
- assertNull("\"No-Op\" button should not be shown in Pip menu",
- uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_NO_OP))
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
- ?: fail("\"Clear\" button should be shown in Pip menu")
+ uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON)
+ ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
+ assertNull(
+ "\"No-Op\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+ )
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
// ... as well as the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
// Invoking the "Clear" action should remove all the custom actions and their corresponding
// buttons, ...
- uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(
- Components.PipActivity.MENU_ACTION_ON)?.also {
- isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu")
- }
- assertNull("\"Off\" button should not be shown in Pip menu",
- uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_OFF))
- assertNull("\"Clear\" button should not be shown in Pip menu",
- uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_CLEAR))
- assertNull("\"No-Op\" button should not be shown in Pip menu",
- uiDevice.findTvPipMenuElementWithDescription(
- Components.PipActivity.MENU_ACTION_NO_OP))
+ uiDevice
+ .waitUntilTvPipMenuElementWithDescriptionIsGone(ActivityOptions.Pip.MENU_ACTION_ON)
+ ?.also { isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu") }
+ assertNull(
+ "\"Off\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+ )
+ assertNull(
+ "\"Clear\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+ )
+ assertNull(
+ "\"No-Op\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+ )
// ... but the menu should still contain the "Full screen" and "Close" buttons.
assertFullscreenAndCloseButtonsAreShown()
@@ -217,26 +237,32 @@ class TvPipMenuTests : TvPipTestBase() {
enterPip_openMenu_assertShown()
// PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
- ?: fail("\"No-Op\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
- ?: fail("\"Off\" button should be shown in Pip menu")
- uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
- ?: fail("\"Clear\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
+ ?: fail("\"No-Op\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
+ ?: fail("\"Off\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
// ... should also contain the "Full screen" and "Close" buttons, ...
assertFullscreenAndCloseButtonsAreShown()
// ... but should not contain media buttons.
- assertNull("\"Play\" button should not be shown in menu when there are custom actions",
- uiDevice.findTvPipMenuElementWithDescription(playButtonDescription))
- assertNull("\"Pause\" button should not be shown in menu when there are custom actions",
- uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription))
-
- uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+ assertNull(
+ "\"Play\" button should not be shown in menu when there are custom actions",
+ uiDevice.findTvPipMenuElementWithDescription(playButtonDescription)
+ )
+ assertNull(
+ "\"Pause\" button should not be shown in menu when there are custom actions",
+ uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
+ )
+
+ uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
// Invoking the "Clear" action should remove all the custom actions, which should bring up
// media buttons...
uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription)
- ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
- "playing media session.")
+ ?: fail(
+ "\"Pause\" button should be shown in Pip menu if there is an active " +
+ "playing media session."
+ )
// ... while the "Full screen" and "Close" buttons should remain in the menu.
assertFullscreenAndCloseButtonsAreShown()
@@ -244,7 +270,7 @@ class TvPipMenuTests : TvPipTestBase() {
}
private fun enterPip_openMenu_assertShown(): UiObject2 {
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
// Pressing the Window key should bring up Pip menu
uiDevice.pressWindowKey()
return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown")
@@ -252,8 +278,8 @@ class TvPipMenuTests : TvPipTestBase() {
private fun assertFullscreenAndCloseButtonsAreShown() {
uiDevice.findTvPipMenuCloseButton()
- ?: fail("\"Close PIP\" button should be shown in Pip menu")
+ ?: fail("\"Close PIP\" button should be shown in Pip menu")
uiDevice.findTvPipMenuFullscreenButton()
- ?: fail("\"Full screen\" button should be shown in Pip menu")
+ ?: fail("\"Full screen\" button should be shown in Pip menu")
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index bcf38d340867..90406c510bad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -34,8 +34,8 @@ import org.junit.Before
import org.junit.Test
/**
- * Test Pip Notifications on TV.
- * To run this test: `atest WMShellFlickerTests:TvPipNotificationTests`
+ * Test Pip Notifications on TV. To run this test: `atest
+ * WMShellFlickerTests:TvPipNotificationTests`
*/
@RequiresDevice
class TvPipNotificationTests : TvPipTestBase() {
@@ -56,49 +56,58 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_postedAndDismissed() {
testApp.launchViaIntent()
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
- assertNotNull("Pip notification should have been posted",
- waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) })
+ assertNotNull(
+ "Pip notification should have been posted",
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+ )
testApp.closePipWindow()
- assertTrue("Pip notification should have been dismissed",
- waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) })
+ assertTrue(
+ "Pip notification should have been dismissed",
+ waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }
+ )
}
@Test
fun pipNotification_closeIntent() {
testApp.launchViaIntent()
- testApp.clickEnterPipButton()
-
- val notification: StatusBarNotification = waitForNotificationToAppear {
- it.isPipNotificationWithTitle(testApp.appName)
- } ?: fail("Pip notification should have been posted")
-
- notification.deleteIntent?.send()
- ?: fail("Pip notification should contain `delete_intent`")
-
- assertTrue("Pip should have closed by sending the `delete_intent`",
- testApp.waitUntilClosed())
- assertTrue("Pip notification should have been dismissed",
- waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) })
+ testApp.clickEnterPipButton(wmHelper)
+
+ val notification: StatusBarNotification =
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+ ?: fail("Pip notification should have been posted")
+
+ notification.deleteIntent?.send() ?: fail("Pip notification should contain `delete_intent`")
+
+ assertTrue(
+ "Pip should have closed by sending the `delete_intent`",
+ testApp.waitUntilClosed()
+ )
+ assertTrue(
+ "Pip notification should have been dismissed",
+ waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.appName) }
+ )
}
@Test
fun pipNotification_menuIntent() {
- testApp.launchViaIntent()
- testApp.clickEnterPipButton()
+ testApp.launchViaIntent(wmHelper)
+ testApp.clickEnterPipButton(wmHelper)
- val notification: StatusBarNotification = waitForNotificationToAppear {
- it.isPipNotificationWithTitle(testApp.appName)
- } ?: fail("Pip notification should have been posted")
+ val notification: StatusBarNotification =
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+ ?: fail("Pip notification should have been posted")
notification.contentIntent?.send()
?: fail("Pip notification should contain `content_intent`")
- assertNotNull("Pip menu should have been shown after sending `content_intent`",
- uiDevice.waitForTvPipMenu())
+ assertNotNull(
+ "Pip menu should have been shown after sending `content_intent`",
+ uiDevice.waitForTvPipMenu()
+ )
uiDevice.pressBack()
testApp.closePipWindow()
@@ -106,41 +115,44 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_mediaSessionTitle_isDisplayed() {
- testApp.launchViaIntent()
+ testApp.launchViaIntent(wmHelper)
// Start media session and to PiP
testApp.clickStartMediaSessionButton()
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
// Wait for the correct notification to show up...
- waitForNotificationToAppear {
- it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING)
- } ?: fail("Pip notification with media session title should have been posted")
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) }
+ ?: fail("Pip notification with media session title should have been posted")
// ... and make sure "regular" PiP notification is now shown
- assertNull("Regular notification should not have been posted",
- findNotification { it.isPipNotificationWithTitle(testApp.appName) })
+ assertNull(
+ "Regular notification should not have been posted",
+ findNotification { it.isPipNotificationWithTitle(testApp.appName) }
+ )
// Pause the media session. When paused the application updates the title for the media
// session. This change should be reflected in the notification.
testApp.pauseMedia()
// Wait for the "paused" notification to show up...
- waitForNotificationToAppear {
- it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED)
- } ?: fail("Pip notification with media session title should have been posted")
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) }
+ ?: fail("Pip notification with media session title should have been posted")
// ... and make sure "playing" PiP notification is gone
- assertNull("Regular notification should not have been posted",
- findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) })
+ assertNull(
+ "Regular notification should not have been posted",
+ findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PLAYING) }
+ )
// Now stop the media session, which should revert the title to the "default" one.
testApp.stopMedia()
// Wait for the "regular" notification to show up...
- waitForNotificationToAppear {
- it.isPipNotificationWithTitle(testApp.appName)
- } ?: fail("Pip notification with media session title should have been posted")
+ waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) }
+ ?: fail("Pip notification with media session title should have been posted")
// ... and make sure previous ("paused") notification is gone
- assertNull("Regular notification should not have been posted",
- findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) })
+ assertNull(
+ "Regular notification should not have been posted",
+ findNotification { it.isPipNotificationWithTitle(TITLE_MEDIA_SESSION_PAUSED) }
+ )
testApp.closePipWindow()
}
@@ -170,4 +182,4 @@ private val StatusBarNotification.deleteIntent: PendingIntent?
get() = tvExtensions?.getParcelable("delete_intent")
private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean =
- tag == "TvPip" && title == expectedTitle \ No newline at end of file
+ tag == "TvPip" && title == expectedTitle
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 9c3b0fa183b6..6104b7bdacba 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
@@ -20,11 +20,11 @@ import android.app.ActivityManager
import android.app.IActivityManager
import android.app.IProcessObserver
import android.os.SystemClock
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
import android.view.Surface.ROTATION_0
import android.view.Surface.rotationToString
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
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.assumeTrue
@@ -33,6 +33,7 @@ import org.junit.Before
abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATION_0) {
private val systemUiProcessObserver = SystemUiProcessObserver()
+ protected val wmHelper = WindowManagerStateHelper()
@Before
final override fun televisionSetUp() {
@@ -67,7 +68,8 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
fun start() {
hasDied = false
uiAutomation.adoptShellPermissionIdentity(
- android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ android.Manifest.permission.SET_ACTIVITY_WATCHER
+ )
activityManager.registerProcessObserver(this)
}
@@ -88,4 +90,4 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
companion object {
private const val AFTER_TEXT_PROCESS_CHECK_DELAY = 1_000L // 1 sec
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index 1c663409b913..b0adbe1d07ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -33,32 +33,31 @@ private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "tv_pip_menu_fullscreen_but
private const val FOCUS_ATTEMPTS = 10
private const val WAIT_TIME_MS = 3_000L
-private val TV_PIP_MENU_SELECTOR =
- By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)
+private val TV_PIP_MENU_SELECTOR = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)
private val TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR =
- By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID)
+ By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID)
private val TV_PIP_MENU_CLOSE_BUTTON_SELECTOR =
- By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID)
+ By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID)
private val TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR =
- By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID)
+ By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID)
fun UiDevice.waitForTvPipMenu(): UiObject2? =
- wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
+ wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
fun UiDevice.waitForTvPipMenuToClose(): Boolean =
- wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
+ wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)
fun UiDevice.findTvPipMenuControls(): UiObject2? =
- findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR)
+ findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR)
fun UiDevice.findTvPipMenuCloseButton(): UiObject2? =
- findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
+ findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? =
- findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)
+ findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)
fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? =
- findTvPipMenuElement(By.desc(desc))
+ findTvPipMenuElement(By.desc(desc))
private fun UiDevice.findTvPipMenuElement(selector: BySelector): UiObject2? =
findObject(TV_PIP_MENU_SELECTOR)?.findObject(selector)
@@ -70,11 +69,10 @@ fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
// descendant and then retrieve the element from the menu and return to the caller of this
// method.
val elementSelector = By.desc(desc)
- val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR)
- .hasDescendant(elementSelector)
+ val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector)
return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS)
- ?.findObject(elementSelector)
+ ?.findObject(elementSelector)
}
fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? {
@@ -86,18 +84,17 @@ fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boole
fun UiDevice.clickTvPipMenuCloseButton() {
focusOnAndClickTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) ||
- error("Could not focus on the Close button")
+ error("Could not focus on the Close button")
}
fun UiDevice.clickTvPipMenuFullscreenButton() {
focusOnAndClickTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) ||
- error("Could not focus on the Fullscreen button")
+ error("Could not focus on the Fullscreen button")
}
fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) {
- focusOnAndClickTvPipMenuElement(By.desc(desc)
- .pkg(SYSTEM_UI_PACKAGE_NAME)) ||
- error("Could not focus on the Pip menu object with \"$desc\" description")
+ focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) ||
+ error("Could not focus on the Pip menu object with \"$desc\" description")
// So apparently Accessibility framework on TV is not very reliable and sometimes the state of
// the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of
// the "real" state of the "UI tree". It seems, however, that moving focus around the tree
@@ -110,7 +107,8 @@ fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) {
private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Boolean {
repeat(FOCUS_ATTEMPTS) {
- val element = findTvPipMenuElement(selector)
+ val element =
+ findTvPipMenuElement(selector)
?: error("The Pip Menu element we try to focus on is gone.")
if (element.isFocusedOrHasFocusedChild) {
@@ -119,10 +117,11 @@ private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Bool
}
findTvPipMenuElement(By.focused(true))?.let { focused ->
- if (element.visibleCenter.x < focused.visibleCenter.x)
- pressDPadLeft() else pressDPadRight()
+ if (element.visibleCenter.x < focused.visibleCenter.x) pressDPadLeft()
+ else pressDPadRight()
waitForIdle()
- } ?: error("Pip menu does not contain a focused element")
+ }
+ ?: error("Pip menu does not contain a focused element")
}
return false
@@ -155,9 +154,8 @@ private fun UiDevice.moveFocus() {
fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW)
-fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
- height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
-}
+fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean =
+ visibleBounds.run { height() == uiDevice.displayHeight && width() == uiDevice.displayWidth }
val UiObject2.isFocusedOrHasFocusedChild: Boolean
get() = isFocused || findObject(By.focused(true)) != null
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
new file mode 100644
index 000000000000..0c9c16153ea3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.EdgeExtensionComponentMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test copy content from the left to the right side of the split-screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
+ private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+ private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+ private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+ transitions {
+ SplitScreenUtils.copyContentInSplit(
+ instrumentation,
+ device,
+ primaryApp,
+ textEditApp
+ )
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() {
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
+
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
+
+ // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+ @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
+
+ @Presubmit @Test fun textEditAppLayerKeepVisible() = flicker.layerKeepVisible(textEditApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsKeepVisible() =
+ flicker.splitAppLayerBoundsKeepVisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun textEditAppBoundsKeepVisible() =
+ flicker.splitAppLayerBoundsKeepVisible(
+ textEditApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
+
+ @Presubmit @Test fun textEditAppWindowKeepVisible() = flicker.appWindowKeepVisible(textEditApp)
+
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ ignoreLayers =
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ ComponentNameMatcher.IME_SNAPSHOT,
+ EdgeExtensionComponentMatcher(),
+ MagnifierLayer,
+ PopupWindowLayer
+ )
+ )
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 264241018)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
new file mode 100644
index 000000000000..1b55f3975e1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ if (tapl.isTablet) {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
+ } else {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
+ }
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsFullscreenAtEnd() {
+ flicker.assertLayers {
+ this.isVisible(secondaryApp).then().isInvisible(secondaryApp).then().invoke(
+ "secondaryAppBoundsIsFullscreenAtEnd"
+ ) {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
+ it.visibleRegion(secondaryApp).coversExactly(displayBounds)
+ }
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
new file mode 100644
index 000000000000..bd2ffc1a018d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by go home.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ }
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
+
+ @FlakyTest(bugId = 241525302)
+ @Test
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
+
+ // TODO(b/245472831): Move back to presubmit after shell transitions landing.
+ @FlakyTest(bugId = 245472831)
+ @Test
+ fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
+
+ // TODO(b/245472831): Move back to presubmit after shell transitions landing.
+ @FlakyTest(bugId = 245472831)
+ @Test
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @FlakyTest(bugId = 250530241)
+ @Test
+ fun secondaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 251268711)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
new file mode 100644
index 000000000000..7db5ecc484ad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -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.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test resize split by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+ }
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() {
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
+
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
+
+ // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerKeepVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.layerKeepVisible(primaryApp)
+ }
+
+ @FlakyTest(bugId = 263213649)
+ @Test
+ fun primaryAppLayerKeepVisible_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.layerKeepVisible(primaryApp)
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerVisibilityChanges() {
+ flicker.assertLayers {
+ this.isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ }
+ }
+
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsChanges() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.splitAppLayerBoundsChanges(
+ primaryApp,
+ landscapePosLeft = true,
+ portraitPosTop = false
+ )
+ }
+
+ @FlakyTest(bugId = 263213649)
+ @Test
+ fun primaryAppBoundsChanges_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.splitAppLayerBoundsChanges(
+ primaryApp,
+ landscapePosLeft = true,
+ portraitPosTop = false
+ )
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsChanges() =
+ flicker.splitAppLayerBoundsChanges(
+ secondaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = true
+ )
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 263213649)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 702710caded7..ffdb87f190d5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,22 +16,25 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
+import android.tools.common.NavBar
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,8 +44,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from all apps.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from all apps. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
*/
@@ -50,74 +53,141 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class EnterSplitScreenByDragFromAllApps(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
- open fun before() {
- Assume.assumeTrue(taplInstrumentation.isTablet)
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
}
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- eachRun {
- taplInstrumentation.goHome()
- primaryApp.launchViaIntent(wmHelper)
- }
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
}
transitions {
- taplInstrumentation.launchedAppState.taskbar
+ tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.component.packageName,
- primaryApp.component.packageName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
+ @IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+ fun splitScreenDividerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.splitScreenDividerBecomesVisible()
+ }
+ // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+ fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+ }
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp.component, false /* splitLeftTop */)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp.component)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 7323d992ecd4..792e2b03522f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,23 +16,24 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
+import android.tools.common.NavBar
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -42,8 +43,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from notification.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from notification. This test is only for large
+ * screen devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
*/
@@ -51,88 +52,163 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class EnterSplitScreenByDragFromNotification(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
- private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+ private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@Before
fun before() {
- Assume.assumeTrue(taplInstrumentation.isTablet)
+ Assume.assumeTrue(tapl.isTablet)
}
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- eachRun {
- // Send a notification
- sendNotificationApp.launchViaIntent(wmHelper)
- val sendNotification = device.wait(
- Until.findObject(By.text("Send Notification")),
- SplitScreenHelper.TIMEOUT_MS
- )
- sendNotification?.click() ?: error("Send notification button not found")
-
- taplInstrumentation.goHome()
- primaryApp.launchViaIntent(wmHelper)
- }
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ sendNotificationApp.postNotification(wmHelper)
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
}
transitions {
- SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
- }
- teardown {
- eachRun {
- sendNotificationApp.exit(wmHelper)
- }
+ SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
+ teardown { sendNotificationApp.exit(wmHelper) }
}
+ @IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+ fun splitScreenDividerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.splitScreenDividerBecomesVisible()
+ }
+ // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() =
- testSpec.layerBecomesVisible(sendNotificationApp.component)
+ fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+ }
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
- )
+ fun secondaryAppLayerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.assertLayers {
+ this.isInvisible(sendNotificationApp)
+ .then()
+ .isVisible(sendNotificationApp)
+ .then()
+ .isInvisible(sendNotificationApp)
+ .then()
+ .isVisible(sendNotificationApp)
+ }
+ }
+ // TODO(b/245472831): Align to legacy transition after shell transition ready.
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */
- )
+ fun secondaryAppLayerBecomesVisible_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(sendNotificationApp)
+ }
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() =
- testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(sendNotificationApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(sendNotificationApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 000000000000..c1977e9e82f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging a shortcut. This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .openDeepShortcutMenu()
+ .getMenuItem("Split Screen Secondary Activity")
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() {
+ flicker.assertWm {
+ this.notContains(secondaryApp)
+ .then()
+ .isAppWindowInvisible(secondaryApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(secondaryApp)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 241523824)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 05c6e24ee89d..5c9920970761 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,22 +16,25 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
+import android.tools.common.NavBar
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,8 +44,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from taskbar.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
*/
@@ -50,79 +53,160 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class EnterSplitScreenByDragFromTaskbar(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
- Assume.assumeTrue(taplInstrumentation.isTablet)
+ Assume.assumeTrue(tapl.isTablet)
}
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- eachRun {
- taplInstrumentation.goHome()
- SplitScreenHelper.createShortcutOnHotseatIfNotExist(
- taplInstrumentation, secondaryApp.appName
- )
- primaryApp.launchViaIntent(wmHelper)
- }
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
}
transitions {
- taplInstrumentation.launchedAppState.taskbar
+ tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(
- secondaryApp.component.packageName,
- primaryApp.component.packageName
- )
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
+ @IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
+ appExistAtStart = false)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+ fun splitScreenDividerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.splitScreenDividerBecomesVisible()
+ }
+ // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+ fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+ }
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
- )
+ fun secondaryAppLayerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.assertLayers {
+ this.isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ }
+ }
+
+ // TODO(b/245472831): Align to legacy transition after shell transition ready.
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(secondaryApp)
+ }
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */
- )
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp.component)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
new file mode 100644
index 000000000000..c45387722a49
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen from Overview.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ transitions {
+ SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() {
+ flicker.splitAppLayerBoundsBecomesVisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 251269324)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 252736515)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 52c2daf96a3c..7abdc06820d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -16,44 +16,29 @@
package com.android.wm.shell.flicker.splitscreen
-import android.app.Instrumentation
import android.content.Context
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.BaseTest
-abstract class SplitScreenBase(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val taplInstrumentation = LauncherInstrumentation()
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
- protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+ protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- protected open val transition: FlickerBuilder.() -> Unit
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
- test {
- taplInstrumentation.setEnableRotation(true)
- setRotation(testSpec.startRotation)
- taplInstrumentation.setExpectedRotation(testSpec.startRotation)
- }
+ tapl.setEnableRotation(true)
+ setRotation(flicker.scenario.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
}
teardown {
- eachRun {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- }
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
new file mode 100644
index 000000000000..7901f7502e2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.tools.common.datatypes.component.ComponentNameMatcher
+import android.tools.common.datatypes.component.IComponentMatcher
+import android.tools.common.datatypes.component.IComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
+
+internal object SplitScreenUtils {
+ private const val TIMEOUT_MS = 3_000L
+ private const val DRAG_DURATION_MS = 1_000L
+ private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+ private const val DIVIDER_BAR = "docked_divider_handle"
+ private const val OVERVIEW_SNAPSHOT = "snapshot"
+ private const val GESTURE_STEP_MS = 16L
+ private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+ private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+ private val notificationScrollerSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+ private val notificationContentSelector: BySelector
+ get() = By.text("Flicker Test Notification")
+ private val dividerBarSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+ private val overviewSnapshotSelector: BySelector
+ get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+ fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
+
+ fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Secondary.LABEL,
+ ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+ )
+
+ fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+ NonResizeableAppHelper(instrumentation)
+
+ fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+ NotificationAppHelper(instrumentation)
+
+ fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+ fun waitForSplitComplete(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: IComponentMatcher,
+ secondaryApp: IComponentMatcher,
+ ) {
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceAppeared(primaryApp)
+ .withWindowSurfaceAppeared(secondaryApp)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ }
+
+ fun enterSplit(
+ wmHelper: WindowManagerStateHelper,
+ tapl: LauncherInstrumentation,
+ device: UiDevice,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
+ ) {
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ splitFromOverview(tapl, device)
+ waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+ // Note: The initial split position in landscape is different between tablet and phone.
+ // In landscape, tablet will let the first app split to right side, and phone will
+ // split to left side.
+ if (tapl.isTablet) {
+ // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+ // contains more than 3 task views. We need to use uiautomator directly to find the
+ // second task to split.
+ tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+ if (snapshots == null || snapshots.size < 1) {
+ error("Fail to find a overview snapshot to split.")
+ }
+
+ // Find the second task in the upper right corner in split select mode by sorting
+ // 'left' in descending order and 'top' in ascending order.
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left
+ }
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
+ snapshots[0].click()
+ } else {
+ tapl.workspace
+ .switchToOverview()
+ .currentTask
+ .tapMenu()
+ .tapSplitMenuItem()
+ .currentTask
+ .open()
+ }
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun dragFromNotificationToSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds =
+ wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+ ?: error("Display not found")
+
+ // Pull down the notifications
+ device.swipe(
+ displayBounds.centerX(),
+ 5,
+ displayBounds.centerX(),
+ displayBounds.bottom,
+ 50 /* steps */
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+
+ // Find the target notification
+ val notificationScroller =
+ device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+ ?: error("Unable to find view $notificationScrollerSelector")
+ var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+ while (notificationContent == null) {
+ device.swipe(
+ displayBounds.centerX(),
+ displayBounds.centerY(),
+ displayBounds.centerX(),
+ displayBounds.centerY() - 150,
+ 20 /* steps */
+ )
+ notificationContent = notificationScroller.findObject(notificationContentSelector)
+ }
+
+ // Drag to split
+ val dragStart = notificationContent.visibleCenter
+ val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+ val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+ val downTime = SystemClock.uptimeMillis()
+
+ touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+ // It needs a horizontal movement to trigger the drag
+ touchMove(
+ instrumentation,
+ downTime,
+ SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS,
+ dragStart,
+ dragMiddle
+ )
+ touchMove(
+ instrumentation,
+ downTime,
+ SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS,
+ dragMiddle,
+ dragEnd
+ )
+ // Wait for a while to start splitting
+ SystemClock.sleep(TIMEOUT_MS)
+ touch(
+ instrumentation,
+ MotionEvent.ACTION_UP,
+ downTime,
+ SystemClock.uptimeMillis(),
+ GESTURE_STEP_MS,
+ dragEnd
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun touch(
+ instrumentation: Instrumentation,
+ action: Int,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ point: Point
+ ) {
+ val motionEvent =
+ MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+ motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+ motionEvent.recycle()
+ SystemClock.sleep(duration)
+ }
+
+ fun touchMove(
+ instrumentation: Instrumentation,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ from: Point,
+ to: Point
+ ) {
+ val steps: Long = duration / GESTURE_STEP_MS
+ var currentTime = eventTime
+ var currentX = from.x.toFloat()
+ var currentY = from.y.toFloat()
+ val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+ val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+ for (i in 1..steps) {
+ val motionMove =
+ MotionEvent.obtain(
+ downTime,
+ currentTime,
+ MotionEvent.ACTION_MOVE,
+ currentX,
+ currentY,
+ 0
+ )
+ motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+ motionMove.recycle()
+
+ currentTime += GESTURE_STEP_MS
+ if (i == steps - 1) {
+ currentX = to.x.toFloat()
+ currentY = to.y.toFloat()
+ } else {
+ currentX += stepX
+ currentY += stepY
+ }
+ SystemClock.sleep(GESTURE_STEP_MS)
+ }
+ }
+
+ fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+ tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+ val allApps = tapl.workspace.switchToAllApps()
+ allApps.freeze()
+ try {
+ allApps.getAppIcon(appName).dragToHotseat(0)
+ } finally {
+ allApps.unfreeze()
+ }
+ }
+
+ fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ val displayBounds =
+ wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
+
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+ .waitForAndVerify()
+ }
+
+ fun dragDividerToDismissSplit(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper,
+ dragToRight: Boolean,
+ dragToBottom: Boolean
+ ) {
+ val displayBounds =
+ wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(
+ Point(
+ if (dragToRight) {
+ displayBounds.width * 4 / 5
+ } else {
+ displayBounds.width * 1 / 5
+ },
+ if (dragToBottom) {
+ displayBounds.height * 4 / 5
+ } else {
+ displayBounds.height * 1 / 5
+ }
+ )
+ )
+ }
+
+ fun doubleTapDividerToSwitch(device: UiDevice) {
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ val interval =
+ (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+ dividerBar.click()
+ SystemClock.sleep(interval.toLong())
+ dividerBar.click()
+ }
+
+ fun copyContentInSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ sourceApp: IComponentNameMatcher,
+ destinationApp: IComponentNameMatcher,
+ ) {
+ // Copy text from sourceApp
+ val textView =
+ device.wait(
+ Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+ TIMEOUT_MS
+ )
+ assertNotNull("Unable to find the TextView", textView)
+ textView.click(LONG_PRESS_TIME_MS)
+
+ val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+ assertNotNull("Unable to find the copy button", copyBtn)
+ copyBtn.click()
+
+ // Paste text to destinationApp
+ val editText =
+ device.wait(
+ Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+ TIMEOUT_MS
+ )
+ assertNotNull("Unable to find the EditText", editText)
+ editText.click(LONG_PRESS_TIME_MS)
+
+ val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+ assertNotNull("Unable to find the paste button", pasteBtn)
+ pasteBtn.click()
+
+ // Verify text
+ if (!textView.text.contentEquals(editText.text)) {
+ error("Fail to copy content in split")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 000000000000..fbb7c7159234
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test double tap the divider bar to switch the two apps.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ SplitScreenUtils.doubleTapDividerToSwitch(device)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ waitForLayersToSwitch(wmHelper)
+ waitForWindowsToSwitch(wmHelper)
+ }
+ }
+
+ private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
+ } else {
+ return@add if (flicker.scenario.isTablet) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
+ } else {
+ return@add if (flicker.scenario.isTablet) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun isLandscape(rotation: Rotation): Boolean {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return displayBounds.width > displayBounds.height
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() {
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
+
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
+
+ // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 000000000000..d675bfb0119d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch to split pair from another app.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
+ val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ thirdApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
new file mode 100644
index 000000000000..2855c71518eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch to split pair from home.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @FlakyTest
+ @Test
+ fun primaryAppLayerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(primaryApp)
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisibleShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(primaryApp)
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 252736515)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 000000000000..c29a917c4e7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.isShellTransitionsEnabled
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ transitions {
+ tapl.workspace.switchToOverview().currentTask.open()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @FlakyTest
+ @Test
+ fun primaryAppLayerBecomesVisible() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(primaryApp)
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisibleShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ flicker.layerBecomesVisible(primaryApp)
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
new file mode 100644
index 000000000000..4c96b3a319d5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch between two split pairs.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
+ private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+ private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ teardown {
+ thirdApp.exit(wmHelper)
+ fourthApp.exit(wmHelper)
+ }
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() {
+ flicker.appWindowIsVisibleAtStart(thirdApp)
+ flicker.appWindowIsVisibleAtStart(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
+
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.appWindowIsInvisibleAtEnd(thirdApp)
+ flicker.appWindowIsInvisibleAtEnd(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerInvisibleAtMiddle() =
+ flicker.assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun thirdAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(thirdApp)
+
+ @FlakyTest(bugId = 247095572)
+ @Test
+ fun fourthAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(fourthApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ @Presubmit
+ @Test
+ fun thirdAppBoundsIsVisibleAtBegin() =
+ flicker.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ thirdApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false,
+ flicker.scenario.startRotation
+ )
+ }
+
+ @Presubmit
+ @Test
+ fun fourthAppBoundsIsVisibleAtBegin() =
+ flicker.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ fourthApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true,
+ flicker.scenario.startRotation
+ )
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun thirdAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(thirdApp)
+
+ @Presubmit
+ @Test
+ fun fourthAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(fourthApp)
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 251268711)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
deleted file mode 100644
index ea606df1536d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
+++ /dev/null
@@ -1,35 +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 {
- // 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: "WMShellFlickerTestApp",
- srcs: ["**/*.java"],
- sdk_version: "current",
- test_suites: ["device-tests"],
-}
-
-java_library {
- name: "wmshell-flicker-test-components",
- srcs: ["src/**/Components.java"],
- sdk_version: "test_current",
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
deleted file mode 100644
index bc0b0b6292b4..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ /dev/null
@@ -1,147 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.testapp">
-
- <uses-sdk android:minSdkVersion="29"
- android:targetSdkVersion="29"/>
- <application android:allowBackup="false"
- android:supportsRtl="true">
- <activity android:name=".FixedActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:launchMode="singleTop"
- android:theme="@style/CutoutShortEdges"
- android:label="FixedApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity android:name=".PipActivity"
- android:resizeableActivity="true"
- 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">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <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">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <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>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <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>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SendNotificationActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SendNotificationApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".NonResizeableActivity"
- android:resizeableActivity="false"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="NonResizeableApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".SimpleActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
- android:theme="@style/CutoutShortEdges"
- android:label="SimpleApp"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity
- android:name=".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/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
deleted file mode 100644
index d424a17b4157..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
deleted file mode 100644
index b43f31da748d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
deleted file mode 100644
index 0e8c7a0fe64a..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<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="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
- android:fillColor="#000000"
- android:fillType="evenOdd"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
deleted file mode 100644
index f8b0ca3da26e..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <Button
- android:id="@+id/button_finish"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginStart="8dp"
- android:text="Finish" />
- <Button
- android:id="@+id/button_new_task"
- android:layout_width="wrap_content"
- android:layout_height="46dp"
- android:layout_marginStart="8dp"
- android:text="New Task" />
- <Button
- android:id="@+id/button_new_bubble"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:text="New Bubble" />
-
- <Button
- android:id="@+id/button_activity_for_result"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginStart="8dp"
- android:text="Activity For Result" />
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
deleted file mode 100644
index 4708cfd48381..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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="match_parent"
- android:layout_height="match_parent"
- android:focusableInTouchMode="true"
- android:background="@android:color/holo_green_light">
- <EditText android:id="@+id/plain_text_input"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:inputType="text"/>
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
deleted file mode 100644
index f23c46455c63..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/black">
-
- <Button
- android:id="@+id/button_create"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:text="Add Bubble" />
-
- <Button
- android:id="@+id/button_cancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/button_create"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dp"
- android:text="Cancel Bubble" />
-
- <Button
- android:id="@+id/button_cancel_all"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/button_cancel"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dp"
- android:text="Cancel All Bubble" />
-</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
deleted file mode 100644
index 45d5917f86d6..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_orange_light">
-
- <TextView
- android:id="@+id/NonResizeableTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:text="NonResizeableActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
deleted file mode 100644
index 8d59b567e59b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/black">
-
- <Button
- android:id="@+id/button_send_notification"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:text="Send Notification" />
-</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
deleted file mode 100644
index 229098313afa..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ /dev/null
@@ -1,141 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_blue_bright">
-
- <!-- All the buttons (and other clickable elements) should be arranged in a way so that it is
- possible to "cycle" over all them by clicking on the D-Pad DOWN button. The way we do it
- here is by arranging them this vertical LL and by relying on the nextFocusDown attribute
- where things are arranged differently and to circle back up to the top once we reach the
- bottom. -->
-
- <Button
- android:id="@+id/enter_pip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Enter PIP"
- android:onClick="enterPip"/>
-
- <CheckBox
- android:id="@+id/with_custom_actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="With custom actions"/>
-
- <RadioGroup
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:checkedButton="@id/enter_pip_on_leave_disabled">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Enter PiP on home press"/>
-
- <RadioButton
- android:id="@+id/enter_pip_on_leave_disabled"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Disabled"
- android:onClick="onAutoPipSelected"/>
-
- <RadioButton
- android:id="@+id/enter_pip_on_leave_manual"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Via code behind"
- android:onClick="onAutoPipSelected"/>
-
- <RadioButton
- android:id="@+id/enter_pip_on_leave_autoenter"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Auto-enter PiP"
- android:onClick="onAutoPipSelected"/>
- </RadioGroup>
-
- <RadioGroup
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:checkedButton="@id/ratio_default">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Ratio"/>
-
- <RadioButton
- android:id="@+id/ratio_default"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Default"
- android:onClick="onRatioSelected"/>
-
- <RadioButton
- android:id="@+id/ratio_square"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Square [1:1]"
- android:onClick="onRatioSelected"/>
-
- <RadioButton
- android:id="@+id/ratio_wide"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Wide [2:1]"
- android:onClick="onRatioSelected"/>
-
- <RadioButton
- android:id="@+id/ratio_tall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Tall [1:2]"
- android:onClick="onRatioSelected"/>
- </RadioGroup>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Media Session"/>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <Button
- android:id="@+id/media_session_start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:nextFocusDown="@id/media_session_stop"
- android:text="Start"/>
-
- <Button
- android:id="@+id/media_session_stop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:nextFocusDown="@id/enter_pip"
- android:text="Stop"/>
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
deleted file mode 100644
index 5d94e5177dcc..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/holo_orange_light">
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
deleted file mode 100644
index 84789f5a6c02..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_green_light">
-
- <TextView
- android:id="@+id/SplitScreenTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:text="PrimaryActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
deleted file mode 100644
index 674bb70ad01e..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@android:color/holo_blue_light">
-
- <TextView
- android:id="@+id/SplitScreenTest"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center_vertical|center_horizontal"
- android:text="SecondaryActivity"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
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
deleted file mode 100644
index 23b51cc06f04..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<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/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
deleted file mode 100644
index bc3bc75ab903..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Toast;
-
-public class BubbleActivity extends Activity {
- private int mNotifId = 0;
-
- public BubbleActivity() {
- super();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- if (intent != null) {
- mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1);
- } else {
- mNotifId = -1;
- }
-
- setContentView(R.layout.activity_bubble);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED";
- Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show();
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
deleted file mode 100644
index 6cd93eff2803..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.drawable.Icon;
-import android.os.SystemClock;
-import android.service.notification.StatusBarNotification;
-import android.view.WindowManager;
-
-import java.util.HashMap;
-
-public class BubbleHelper {
-
- static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID";
- static final String CHANNEL_ID = "bubbles";
- static final String CHANNEL_NAME = "Bubbles";
- static final int DEFAULT_HEIGHT_DP = 300;
-
- private static BubbleHelper sInstance;
-
- private final Context mContext;
- private NotificationManager mNotificationManager;
- private float mDisplayHeight;
-
- private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>();
-
- private int mNextNotifyId = 0;
- private int mColourIndex = 0;
-
- public static class BubbleInfo {
- public int id;
- public int height;
- public Icon icon;
-
- public BubbleInfo(int id, int height, Icon icon) {
- this.id = id;
- this.height = height;
- this.icon = icon;
- }
- }
-
- public static BubbleHelper getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new BubbleHelper(context);
- }
- return sInstance;
- }
-
- private BubbleHelper(Context context) {
- mContext = context;
- mNotificationManager = context.getSystemService(NotificationManager.class);
-
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setDescription("Channel that posts bubbles");
- channel.setAllowBubbles(true);
- mNotificationManager.createNotificationChannel(channel);
-
- Point p = new Point();
- WindowManager wm = context.getSystemService(WindowManager.class);
- wm.getDefaultDisplay().getRealSize(p);
- mDisplayHeight = p.y;
-
- }
-
- private int getNextNotifyId() {
- int id = mNextNotifyId;
- mNextNotifyId++;
- return id;
- }
-
- private Icon getIcon() {
- return Icon.createWithResource(mContext, R.drawable.bg);
- }
-
- public int addNewBubble(boolean autoExpand, boolean suppressNotif) {
- int id = getNextNotifyId();
- BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon());
- mBubbleMap.put(info.id, info);
-
- Notification.BubbleMetadata data = getBubbleBuilder(info)
- .setSuppressNotification(suppressNotif)
- .setAutoExpandBubble(false)
- .build();
- Notification notification = getNotificationBuilder(info.id)
- .setBubbleMetadata(data).build();
-
- mNotificationManager.notify(info.id, notification);
- return info.id;
- }
-
- private Notification.Builder getNotificationBuilder(int id) {
- Person chatBot = new Person.Builder()
- .setBot(true)
- .setName("BubbleChat")
- .setImportant(true)
- .build();
- String shortcutId = "BubbleChat";
- return new Notification.Builder(mContext, CHANNEL_ID)
- .setChannelId(CHANNEL_ID)
- .setShortcutId(shortcutId)
- .setContentTitle("BubbleChat")
- .setContentIntent(PendingIntent.getActivity(mContext, 0,
- new Intent(mContext, LaunchBubbleActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT))
- .setStyle(new Notification.MessagingStyle(chatBot)
- .setConversationTitle("BubbleChat")
- .addMessage("BubbleChat",
- SystemClock.currentThreadTimeMillis() - 300000, chatBot)
- .addMessage("Is it me, " + id + ", you're looking for?",
- SystemClock.currentThreadTimeMillis(), chatBot)
- )
- .setSmallIcon(R.drawable.ic_bubble);
- }
-
- private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) {
- Intent target = new Intent(mContext, BubbleActivity.class);
- target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id);
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- return new Notification.BubbleMetadata.Builder()
- .setIntent(bubbleIntent)
- .setIcon(info.icon)
- .setDesiredHeight(info.height);
- }
-
- public void cancel(int id) {
- mNotificationManager.cancel(id);
- }
-
- public void cancelAll() {
- mNotificationManager.cancelAll();
- }
-
- public void cancelLast() {
- StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
- if (activeNotifications.length > 0) {
- mNotificationManager.cancel(
- activeNotifications[activeNotifications.length - 1].getId());
- }
- }
-
- public void cancelFirst() {
- StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
- if (activeNotifications.length > 0) {
- mNotificationManager.cancel(activeNotifications[0].getId());
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
deleted file mode 100644
index a2b580da5898..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.content.ComponentName;
-
-public class Components {
- public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
-
- public static class SimpleActivity {
- public static final String LABEL = "SimpleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SimpleActivity");
- }
-
- public static class FixedActivity {
- public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
- public static final String LABEL = "FixedApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".FixedActivity");
- }
-
- public static class NonResizeableActivity {
- public static final String LABEL = "NonResizeableApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".NonResizeableActivity");
- }
-
- public static class PipActivity {
- // Test App > Pip Activity
- public static final String LABEL = "PipApp";
- public static final String MENU_ACTION_NO_OP = "No-Op";
- public static final String MENU_ACTION_ON = "On";
- public static final String MENU_ACTION_OFF = "Off";
- public static final String MENU_ACTION_CLEAR = "Clear";
-
- // Intent action that this activity dynamically registers to enter picture-in-picture
- public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
- // Intent action that this activity dynamically registers to set requested orientation.
- // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
- public static final String ACTION_SET_REQUESTED_ORIENTATION =
- PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
-
- // Calls enterPictureInPicture() on creation
- public static final String EXTRA_ENTER_PIP = "enter_pip";
- // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
- public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
- // Adds a click listener to finish this activity when it is clicked
- public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".PipActivity");
- }
-
- public static class ImeActivity {
- public static final String LABEL = "ImeApp";
- public static final String ACTION_CLOSE_IME =
- PACKAGE_NAME + ".action.CLOSE_IME";
- public static final String ACTION_OPEN_IME =
- PACKAGE_NAME + ".action.OPEN_IME";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".ImeActivity");
- }
-
- public static class SplitScreenActivity {
- public static final String LABEL = "SplitScreenPrimaryApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SplitScreenActivity");
- }
-
- public static class SplitScreenSecondaryActivity {
- public static final String LABEL = "SplitScreenSecondaryApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SplitScreenSecondaryActivity");
- }
-
- public static class SendNotificationActivity {
- public static final String LABEL = "SendNotificationApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".SendNotificationActivity");
- }
-
- public static class LaunchBubbleActivity {
- public static final String LABEL = "LaunchBubbleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".LaunchBubbleActivity");
- }
-
- public static class BubbleActivity {
- public static final String LABEL = "BubbleApp";
- public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
- PACKAGE_NAME + ".BubbleActivity");
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
deleted file mode 100644
index d4ae6c1313bf..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
-
-import android.os.Bundle;
-
-public class FixedActivity extends SimpleActivity {
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Set the fixed orientation if requested
- if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
- final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
- setRequestedOrientation(ori);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
deleted file mode 100644
index 59c64a1345ab..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-
-public class ImeActivity extends Activity {
- private static final String ACTION_OPEN_IME =
- "com.android.wm.shell.flicker.testapp.action.OPEN_IME";
- private static final String ACTION_CLOSE_IME =
- "com.android.wm.shell.flicker.testapp.action.CLOSE_IME";
-
- private InputMethodManager mImm;
- private View mEditText;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
- setContentView(R.layout.activity_ime);
-
- mEditText = findViewById(R.id.plain_text_input);
- mImm = getSystemService(InputMethodManager.class);
-
- handleIntent(getIntent());
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- handleIntent(intent);
- }
-
- private void handleIntent(Intent intent) {
- final String action = intent.getAction();
- if (ACTION_OPEN_IME.equals(action)) {
- mEditText.requestFocus();
- mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
- } else if (ACTION_CLOSE_IME.equals(action)) {
- mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- mEditText.clearFocus();
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
deleted file mode 100644
index 71fa66d8a61c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-
-import android.app.Activity;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.view.View;
-
-import java.util.Arrays;
-
-public class LaunchBubbleActivity extends Activity {
-
- private BubbleHelper mBubbleHelper;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addInboxShortcut(getApplicationContext());
- mBubbleHelper = BubbleHelper.getInstance(this);
- setContentView(R.layout.activity_main);
- findViewById(R.id.button_create).setOnClickListener(this::add);
- findViewById(R.id.button_cancel).setOnClickListener(this::cancel);
- findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll);
- }
-
- private void add(View v) {
- mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */);
- }
-
- private void cancel(View v) {
- mBubbleHelper.cancelLast();
- }
-
- private void cancelAll(View v) {
- mBubbleHelper.cancelAll();
- }
-
- private void addInboxShortcut(Context context) {
- Icon icon = Icon.createWithResource(this, R.drawable.bg);
- Person[] persons = new Person[4];
- for (int i = 0; i < persons.length; i++) {
- persons[i] = new Person.Builder()
- .setBot(false)
- .setIcon(icon)
- .setName("google" + i)
- .setImportant(true)
- .build();
- }
-
- ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat")
- .setShortLabel("BubbleChat")
- .setLongLived(true)
- .setIntent(new Intent(Intent.ACTION_VIEW))
- .setIcon(Icon.createWithResource(context, R.drawable.ic_message))
- .setPersons(persons)
- .build();
- ShortcutManager scmanager = context.getSystemService(ShortcutManager.class);
- scmanager.addDynamicShortcuts(Arrays.asList(shortcut));
- }
-
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
deleted file mode 100644
index 615b1730579c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import static android.media.MediaMetadata.METADATA_KEY_TITLE;
-import static android.media.session.PlaybackState.ACTION_PAUSE;
-import static android.media.session.PlaybackState.ACTION_PLAY;
-import static android.media.session.PlaybackState.ACTION_STOP;
-import static android.media.session.PlaybackState.STATE_PAUSED;
-import static android.media.session.PlaybackState.STATE_PLAYING;
-import static android.media.session.PlaybackState.STATE_STOPPED;
-
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.util.Log;
-import android.util.Rational;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.CheckBox;
-import android.widget.RadioButton;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-public class PipActivity extends Activity {
- private static final String TAG = PipActivity.class.getSimpleName();
- /**
- * A media session title for when the session is in {@link STATE_PLAYING}.
- * TvPipNotificationTests check whether the actual notification title matches this string.
- */
- private static final String TITLE_STATE_PLAYING = "TestApp media is playing";
- /**
- * A media session title for when the session is in {@link STATE_PAUSED}.
- * TvPipNotificationTests check whether the actual notification title matches this string.
- */
- private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
-
- private static final Rational RATIO_DEFAULT = null;
- private static final Rational RATIO_SQUARE = new Rational(1, 1);
- private static final Rational RATIO_WIDE = new Rational(2, 1);
- private static final Rational RATIO_TALL = new Rational(1, 2);
-
- private static final String PIP_ACTION_NO_OP = "No-Op";
- private static final String PIP_ACTION_OFF = "Off";
- private static final String PIP_ACTION_ON = "On";
- private static final String PIP_ACTION_CLEAR = "Clear";
- private static final String ACTION_NO_OP = "com.android.wm.shell.flicker.testapp.NO_OP";
- private static final String ACTION_SWITCH_OFF =
- "com.android.wm.shell.flicker.testapp.SWITCH_OFF";
- private static final String ACTION_SWITCH_ON = "com.android.wm.shell.flicker.testapp.SWITCH_ON";
- private static final String ACTION_CLEAR = "com.android.wm.shell.flicker.testapp.CLEAR";
-
- private final PictureInPictureParams.Builder mPipParamsBuilder =
- new PictureInPictureParams.Builder()
- .setAspectRatio(RATIO_DEFAULT);
- private MediaSession mMediaSession;
- private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
- .setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
- .setState(STATE_STOPPED, 0, 1f);
- private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
- private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
-
- private final List<RemoteAction> mSwitchOffActions = new ArrayList<>();
- private final List<RemoteAction> mSwitchOnActions = new ArrayList<>();
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (isInPictureInPictureMode()) {
- switch (intent.getAction()) {
- case ACTION_SWITCH_ON:
- mPipParamsBuilder.setActions(mSwitchOnActions);
- break;
- case ACTION_SWITCH_OFF:
- mPipParamsBuilder.setActions(mSwitchOffActions);
- break;
- case ACTION_CLEAR:
- mPipParamsBuilder.setActions(Collections.emptyList());
- break;
- case ACTION_NO_OP:
- return;
- default:
- Log.w(TAG, "Unhandled action=" + intent.getAction());
- return;
- }
- setPictureInPictureParams(mPipParamsBuilder.build());
- } else {
- switch (intent.getAction()) {
- case ACTION_ENTER_PIP:
- enterPip(null);
- break;
- case ACTION_SET_REQUESTED_ORIENTATION:
- setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
- EXTRA_PIP_ORIENTATION)));
- break;
- default:
- Log.w(TAG, "Unhandled action=" + intent.getAction());
- return;
- }
- }
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Window window = getWindow();
- final WindowManager.LayoutParams layoutParams = window.getAttributes();
- layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- window.setAttributes(layoutParams);
-
- setContentView(R.layout.activity_pip);
-
- findViewById(R.id.media_session_start)
- .setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING));
- findViewById(R.id.media_session_stop)
- .setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
-
- mMediaSession = new MediaSession(this, "WMShell_TestApp");
- mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
- mMediaSession.setCallback(new MediaSession.Callback() {
- @Override
- public void onPlay() {
- updateMediaSessionState(STATE_PLAYING);
- }
-
- @Override
- public void onPause() {
- updateMediaSessionState(STATE_PAUSED);
- }
-
- @Override
- public void onStop() {
- updateMediaSessionState(STATE_STOPPED);
- }
- });
-
- // Build two sets of the custom actions. We'll replace one with the other when 'On'/'Off'
- // action is invoked.
- // The first set consists of 3 actions: 1) Off; 2) No-Op; 3) Clear.
- // The second set consists of 2 actions: 1) On; 2) Clear.
- // Upon invocation 'Clear' action clear-off all the custom actions, including itself.
- final Icon icon = Icon.createWithResource(this, android.R.drawable.ic_menu_help);
- final RemoteAction noOpAction = buildRemoteAction(icon, PIP_ACTION_NO_OP, ACTION_NO_OP);
- final RemoteAction switchOnAction =
- buildRemoteAction(icon, PIP_ACTION_ON, ACTION_SWITCH_ON);
- final RemoteAction switchOffAction =
- buildRemoteAction(icon, PIP_ACTION_OFF, ACTION_SWITCH_OFF);
- final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR);
- mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction));
- mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction));
-
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_NO_OP);
- filter.addAction(ACTION_SWITCH_ON);
- filter.addAction(ACTION_SWITCH_OFF);
- filter.addAction(ACTION_CLEAR);
- filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
- filter.addAction(ACTION_ENTER_PIP);
- registerReceiver(mBroadcastReceiver, filter);
-
- handleIntentExtra(getIntent());
- }
-
- @Override
- protected void onDestroy() {
- unregisterReceiver(mBroadcastReceiver);
- super.onDestroy();
- }
-
- @Override
- protected void onUserLeaveHint() {
- // Only used when auto PiP is disabled. This is to simulate the behavior that an app
- // supports regular PiP but not auto PiP.
- final boolean manuallyEnterPip =
- ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked();
- if (manuallyEnterPip) {
- enterPictureInPictureMode();
- }
- }
-
- private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
- final Intent intent = new Intent(action);
- final PendingIntent pendingIntent =
- PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- return new RemoteAction(icon, label, label, pendingIntent);
- }
-
- public void enterPip(View v) {
- final boolean withCustomActions =
- ((CheckBox) findViewById(R.id.with_custom_actions)).isChecked();
- mPipParamsBuilder.setActions(
- withCustomActions ? mSwitchOnActions : Collections.emptyList());
- enterPictureInPictureMode(mPipParamsBuilder.build());
- }
-
- public void onAutoPipSelected(View v) {
- switch (v.getId()) {
- case R.id.enter_pip_on_leave_manual:
- // disable auto enter PiP
- case R.id.enter_pip_on_leave_disabled:
- mPipParamsBuilder.setAutoEnterEnabled(false);
- setPictureInPictureParams(mPipParamsBuilder.build());
- break;
- case R.id.enter_pip_on_leave_autoenter:
- mPipParamsBuilder.setAutoEnterEnabled(true);
- setPictureInPictureParams(mPipParamsBuilder.build());
- break;
- }
- }
-
- public void onRatioSelected(View v) {
- switch (v.getId()) {
- case R.id.ratio_default:
- mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT);
- break;
-
- case R.id.ratio_square:
- mPipParamsBuilder.setAspectRatio(RATIO_SQUARE);
- break;
-
- case R.id.ratio_wide:
- mPipParamsBuilder.setAspectRatio(RATIO_WIDE);
- break;
-
- case R.id.ratio_tall:
- mPipParamsBuilder.setAspectRatio(RATIO_TALL);
- break;
- }
- }
-
- private void updateMediaSessionState(int newState) {
- if (mPlaybackState.getState() == newState) {
- return;
- }
- final String title;
- switch (newState) {
- case STATE_PLAYING:
- title = TITLE_STATE_PLAYING;
- break;
- case STATE_PAUSED:
- title = TITLE_STATE_PAUSED;
- break;
- case STATE_STOPPED:
- title = "";
- break;
-
- default:
- throw new IllegalArgumentException("Unknown state " + newState);
- }
-
- mPlaybackStateBuilder.setState(newState, 0, 1f);
- mPlaybackState = mPlaybackStateBuilder.build();
-
- mMediaMetadataBuilder.putText(METADATA_KEY_TITLE, title);
-
- mMediaSession.setPlaybackState(mPlaybackState);
- mMediaSession.setMetadata(mMediaMetadataBuilder.build());
- mMediaSession.setActive(newState != STATE_STOPPED);
- }
-
- private void handleIntentExtra(Intent intent) {
- // Set the fixed orientation if requested
- if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) {
- final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
- setRequestedOrientation(ori);
- }
- // Enter picture in picture with the given aspect ratio if provided
- if (intent.hasExtra(EXTRA_ENTER_PIP)) {
- mPipParamsBuilder.setActions(mSwitchOnActions);
- enterPip(null);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
deleted file mode 100644
index 8020ef2270a0..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-
-public class SendNotificationActivity extends Activity {
- private NotificationManager mNotificationManager;
- private String mChannelId = "Channel id";
- private String mChannelName = "Channel name";
- private NotificationChannel mChannel;
- private int mNotifyId = 0;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_notification);
- findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification);
-
- mChannel = new NotificationChannel(mChannelId, mChannelName,
- NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationManager = getSystemService(NotificationManager.class);
- mNotificationManager.createNotificationChannel(mChannel);
- }
-
- private void sendNotification(View v) {
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- new Intent(this, SendNotificationActivity.class),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- Notification notification = new Notification.Builder(this, mChannelId)
- .setContentTitle("Notification App")
- .setContentText("Notification content")
- .setWhen(System.currentTimeMillis())
- .setSmallIcon(R.drawable.ic_message)
- .setContentIntent(pendingIntent)
- .build();
-
- mNotificationManager.notify(mNotifyId, notification);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
deleted file mode 100644
index 5343c1893d4e..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class SimpleActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- WindowManager.LayoutParams p = getWindow().getAttributes();
- p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
- .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- getWindow().setAttributes(p);
- setContentView(R.layout.activity_simple);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 2ac1dc0c4838..57a698128d77 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -69,6 +69,8 @@ android_test {
enabled: false,
},
+ test_suites: ["device-tests"],
+
platform_apis: true,
certificate: "platform",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index b5ee037892ba..51a20ee9d090 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
@@ -17,8 +17,10 @@
package com.android.wm.shell;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.testing.TestableContext;
@@ -36,6 +38,7 @@ import org.mockito.MockitoAnnotations;
public abstract class ShellTestCase {
protected TestableContext mContext;
+ private PackageManager mPm;
@Before
public void shellSetup() {
@@ -46,6 +49,7 @@ public abstract class ShellTestCase {
final Context context =
InstrumentationRegistry.getInstrumentation().getTargetContext();
final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ mPm = context.getPackageManager();
mContext = new TestableContext(
context.createDisplayContext(dm.getDisplay(DEFAULT_DISPLAY)));
@@ -66,4 +70,20 @@ public abstract class ShellTestCase {
protected Context getContext() {
return mContext;
}
+
+ /**
+ * Makes an assumption that the test device is a TV device, used to guard tests that should
+ * only be run on TVs.
+ */
+ protected void assumeTelevision() {
+ assumeTrue(isTelevision());
+ }
+
+ /**
+ * Returns whether this test device is a TV device.
+ */
+ protected boolean isTelevision() {
+ return mPm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ || mPm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
+ }
}
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 d5bb901259bd..62bfd17cefba 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
@@ -92,6 +92,7 @@ public class TaskViewTest extends ShellTestCase {
Context mContext;
TaskView mTaskView;
TaskViewTransitions mTaskViewTransitions;
+ TaskViewTaskController mTaskViewTaskController;
@Before
public void setUp() {
@@ -125,7 +126,9 @@ public class TaskViewTest extends ShellTestCase {
doReturn(true).when(mTransitions).isRegistered();
}
mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
- mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
+ mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
+ mTaskViewTransitions, mSyncQueue);
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -138,7 +141,8 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetPendingListener_throwsException() {
- TaskView taskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
+ TaskView taskView = new TaskView(mContext,
+ new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue));
taskView.setListener(mExecutor, mViewListener);
try {
taskView.setListener(mExecutor, mViewListener);
@@ -152,16 +156,17 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testStartActivity() {
ActivityOptions options = ActivityOptions.makeBasic();
- mTaskView.startActivity(mock(PendingIntent.class), null, options, new Rect(0, 0, 100, 100));
+ mTaskView.startActivity(mock(PendingIntent.class), null, options,
+ new Rect(0, 0, 100, 100));
- verify(mOrganizer).setPendingLaunchCookieListener(any(), eq(mTaskView));
+ verify(mOrganizer).setPendingLaunchCookieListener(any(), eq(mTaskViewTaskController));
assertThat(options.getLaunchWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW);
}
@Test
public void testOnTaskAppeared_noSurface_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
verify(mViewListener, never()).onInitialized();
@@ -173,7 +178,7 @@ public class TaskViewTest extends ShellTestCase {
public void testOnTaskAppeared_withSurface_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
assertThat(mTaskView.isInitialized()).isTrue();
@@ -194,7 +199,7 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSurfaceCreated_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
verify(mViewListener).onInitialized();
@@ -216,7 +221,7 @@ public class TaskViewTest extends ShellTestCase {
public void testSurfaceDestroyed_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(sh);
reset(mViewListener);
mTaskView.surfaceDestroyed(sh);
@@ -227,11 +232,11 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testOnReleased_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
mTaskView.release();
- verify(mOrganizer).removeListener(eq(mTaskView));
+ verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
verify(mViewListener).onReleased();
assertThat(mTaskView.isInitialized()).isFalse();
}
@@ -239,9 +244,9 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testOnTaskVanished_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
- mTaskView.onTaskVanished(mTaskInfo);
+ mTaskViewTaskController.onTaskVanished(mTaskInfo);
verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
}
@@ -249,8 +254,8 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testOnBackPressedOnTaskRoot_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
- mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
}
@@ -258,17 +263,17 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetOnBackPressedOnTaskRoot_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
}
@Test
public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
- mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
- mTaskView.onTaskVanished(mTaskInfo);
+ mTaskViewTaskController.onTaskVanished(mTaskInfo);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
}
@@ -276,8 +281,9 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
verify(mViewListener, never()).onInitialized();
@@ -303,8 +309,9 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.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());
@@ -314,15 +321,17 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.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));
+ verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(true));
- mTaskView.prepareOpenAnimation(false /* newTask */, new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+ mTaskViewTaskController.prepareOpenAnimation(false /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
}
@@ -342,15 +351,16 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.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));
+ verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(false));
- mTaskView.prepareHideAnimation(new SurfaceControl.Transaction());
+ mTaskViewTaskController.prepareHideAnimation(new SurfaceControl.Transaction());
verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
}
@@ -359,25 +369,27 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.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(mOrganizer).removeListener(eq(mTaskViewTaskController));
verify(mViewListener).onReleased();
assertThat(mTaskView.isInitialized()).isFalse();
- verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
+ verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController));
}
@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);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
- mTaskView.prepareCloseAnimation();
+ mTaskViewTaskController.prepareCloseAnimation();
verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
}
@@ -386,9 +398,10 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
}
@@ -397,8 +410,9 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
}
@@ -406,11 +420,12 @@ public class TaskViewTest extends ShellTestCase {
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);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
- mTaskView.prepareCloseAnimation();
+ mTaskViewTaskController.prepareCloseAnimation();
verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index fe8b305093d7..da95c77d2b89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -48,10 +48,9 @@ public class TestShellExecutor implements ShellExecutor {
}
public void flushAll() {
- final ArrayList<Runnable> tmpRunnable = new ArrayList<>(mRunnables);
- mRunnables.clear();
- for (Runnable r : tmpRunnable) {
+ for (Runnable r : mRunnables) {
r.run();
}
+ mRunnables.clear();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
new file mode 100644
index 000000000000..26b787fa836c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+
+/**
+ * Utility for creating/editing synthetic TransitionInfos for tests.
+ */
+public class TransitionInfoBuilder {
+ final TransitionInfo mInfo;
+ static final int DISPLAY_ID = 0;
+
+ public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
+ this(type, 0 /* flags */);
+ }
+
+ public TransitionInfoBuilder(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ mInfo = new TransitionInfo(type, flags);
+ mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
+ }
+
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ taskInfo != null ? taskInfo.token : null, createMockSurface(true /* valid */));
+ change.setMode(mode);
+ change.setFlags(flags);
+ change.setTaskInfo(taskInfo);
+ return addChange(change);
+ }
+
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ return addChange(mode, TransitionInfo.FLAG_NONE, taskInfo);
+ }
+
+ public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
+ return addChange(mode, TransitionInfo.FLAG_NONE, null /* taskInfo */);
+ }
+
+ public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
+ mInfo.addChange(change);
+ return this;
+ }
+
+ public TransitionInfo build() {
+ return mInfo;
+ }
+
+ private static SurfaceControl createMockSurface(boolean valid) {
+ SurfaceControl sc = mock(SurfaceControl.class);
+ doReturn(valid).when(sc).isValid();
+ doReturn("TestSurface").when(sc).toString();
+ return sc;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 79070b1469be..a625346e69c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -35,6 +35,8 @@ import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.TransitionInfoBuilder;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,10 +62,9 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
@Test
public void testStartAnimation() {
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .build();
doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
@@ -84,10 +85,9 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
@Test
public void testChangesBehindStartingWindow() {
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_BEHIND_STARTING_WINDOW);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createChange(FLAG_IS_BEHIND_STARTING_WINDOW))
+ .build();
final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction,
() -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 54a12ab999c5..4f4f356ef2e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -82,9 +82,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
}
/** Creates a mock {@link TransitionInfo.Change}. */
- static TransitionInfo.Change createChange() {
- return new TransitionInfo.Change(mock(WindowContainerToken.class),
+ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) {
+ TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class),
mock(SurfaceControl.class));
+ c.setFlags(flags);
+ return c;
}
/**
@@ -93,8 +95,7 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
*/
static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds,
@NonNull Rect endBounds, @NonNull Rect taskBounds) {
- final TransitionInfo.Change change = createChange();
- change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ final TransitionInfo.Change change = createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
change.setStartAbsBounds(startBounds);
change.setEndAbsBounds(endBounds);
if (taskBounds.width() == startBounds.width()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 4d98b6ba4f7a..cbbb29199d75 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -34,6 +34,8 @@ import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.TransitionInfoBuilder;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -80,12 +82,11 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
- EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
- final TransitionInfo.Change nonEmbeddingChange = createChange();
- info.addChange(embeddingChange);
- info.addChange(nonEmbeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+ .addChange(createChange(0 /* flags */))
+ .build();
// No-op because it contains non-embedded change.
assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
@@ -98,10 +99,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS,
- TASK_BOUNDS);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
+ .build();
// No-op because it only contains embedded change that fills the Task. We will let the
// default handler to animate such transition.
@@ -116,10 +116,10 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsActivityEmbeddingSplitChange() {
// Change that occupies only part of the Task.
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
- EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+ .build();
// ActivityEmbeddingController will handle such transition.
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
@@ -133,10 +133,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() {
// Change that is entering ActivityEmbedding split.
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS,
- EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(TASK_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+ .build();
// ActivityEmbeddingController will handle such transition.
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
@@ -150,10 +149,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() {
// Change that is exiting ActivityEmbedding split.
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS,
- TASK_BOUNDS, TASK_BOUNDS);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
+ .build();
// ActivityEmbeddingController will handle such transition.
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
@@ -170,10 +168,10 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
assertThrows(IllegalStateException.class,
() -> mController.onAnimationFinished(mTransition));
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
- EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
- info.addChange(embeddingChange);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+ .build();
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2754496a6f3f..169b9bd4dea7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -18,15 +18,13 @@ package com.android.wm.shell.back;
import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -39,9 +37,8 @@ import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.provider.Settings;
@@ -49,15 +46,17 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContentResolver;
import android.testing.TestableContext;
import android.testing.TestableLooper;
+import android.view.IRemoteAnimationRunner;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
-import android.window.IBackNaviAnimationController;
+import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -69,7 +68,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,23 +92,27 @@ public class BackAnimationControllerTest extends ShellTestCase {
new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
@Mock
- private SurfaceControl.Transaction mTransaction;
+ private IActivityTaskManager mActivityTaskManager;
@Mock
- private IActivityTaskManager mActivityTaskManager;
+ private IOnBackInvokedCallback mAppCallback;
+
+ @Mock
+ private IOnBackInvokedCallback mAnimatorCallback;
@Mock
- private IOnBackInvokedCallback mIOnBackInvokedCallback;
+ private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@Mock
- private IBackNaviAnimationController mIBackNaviAnimationController;
+ private IRemoteAnimationRunner mBackAnimationRunner;
@Mock
private ShellController mShellController;
- private BackAnimationController mController;
+ @Mock
+ private BackAnimationBackground mAnimationBackground;
- private int mEventTime = 0;
+ private BackAnimationController mController;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
@@ -125,29 +127,20 @@ public class BackAnimationControllerTest extends ShellTestCase {
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mController = new BackAnimationController(mShellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+ mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
mController.setEnableUAnimation(true);
mShellInit.init();
- mEventTime = 0;
mShellExecutor.flushAll();
}
- private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
- SurfaceControl screenshotSurface,
- HardwareBuffer hardwareBuffer,
- int backType,
- IOnBackInvokedCallback onBackInvokedCallback, boolean prepareAnimation) {
+ private void createNavigationInfo(int backType, boolean enableAnimation) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
- .setDepartingAnimationTarget(topAnimationTarget)
- .setScreenshotSurface(screenshotSurface)
- .setScreenshotBuffer(hardwareBuffer)
- .setTaskWindowConfiguration(new WindowConfiguration())
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(onBackInvokedCallback)
- .setPrepareAnimation(prepareAnimation);
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation);
createNavigationInfo(builder);
}
@@ -155,7 +148,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void createNavigationInfo(BackNavigationInfo.Builder builder) {
try {
doReturn(builder.build()).when(mActivityTaskManager)
- .startBackNavigation(anyBoolean(), any(), any());
+ .startBackNavigation(any(), any());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -188,75 +181,57 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@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, null, true);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
- verify(mTransaction).setVisibility(screenshotSurface, true);
- verify(mTransaction).apply();
- }
+ public void verifyNavigationFinishes() throws RemoteException {
+ final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK };
+
+ for (int type: testTypes) {
+ registerAnimation(type);
+ }
- @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, null, true);
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- // b/207481538, we check that the surface is not moved for now, we can re-enable this once
- // we implement the animation
- verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt());
- verify(mTransaction, never()).setPosition(
- animationTarget.leash, 100, 100);
- verify(mTransaction, atLeastOnce()).apply();
- }
+ for (int type: testTypes) {
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationFinished();
+ mShellExecutor.flushAll();
- @Test
- public void verifyAnimationFinishes() {
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- boolean[] backNavigationDone = new boolean[]{false};
- boolean[] triggerBack = new boolean[]{false};
- createNavigationInfo(new BackNavigationInfo.Builder()
- .setDepartingAnimationTarget(animationTarget)
- .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
- .setOnBackNavigationDone(
- new RemoteCallback(result -> {
- backNavigationDone[0] = true;
- triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
- })));
- triggerBackGesture();
- assertTrue("Navigation Done callback not called", backNavigationDone[0]);
- assertTrue("TriggerBack should have been true", triggerBack[0]);
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+ }
}
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
+
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
ArgumentCaptor<BackMotionEvent> backEventCaptor =
ArgumentCaptor.forClass(BackMotionEvent.class);
- verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
- assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class));
+ verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
}
@Test
@@ -265,107 +240,222 @@ public class BackAnimationControllerTest extends ShellTestCase {
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
mController = new BackAnimationController(shellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+ mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
shellInit.init();
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
ArgumentCaptor<BackMotionEvent> backEventCaptor =
ArgumentCaptor.forClass(BackMotionEvent.class);
- createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false);
+
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class));
- verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(appCallback, times(1)).onBackInvoked();
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAppCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class));
- verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ verify(mBackAnimationRunner, never()).onAnimationStart(
+ anyInt(), any(), any(), any(), any());
}
@Test
public void ignoresGesture_transitionInProgress() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Check that back invocation is dispatched.
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+
+ reset(mAnimatorCallback);
+ reset(mBackAnimationRunner);
- reset(mIOnBackInvokedCallback);
// Verify that we prevent animation from restarting if another gestures happens before
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
- mController.onBackToLauncherAnimationFinished();
+ verifyNoMoreInteractions(mAnimatorCallback);
+
+ // Finish back navigation.
+ simulateRemoteAnimationFinished();
// Verify that more events from a rejected swipe cannot start animation.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
+ verifyNoMoreInteractions(mAnimatorCallback);
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
+
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ verify(mAnimatorCallback).onBackStarted(any());
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+
+ // In case it is still running in animation.
+ doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- reset(mIOnBackInvokedCallback);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Simulate transition timeout.
mShellExecutor.flushAll();
+ reset(mAnimatorCallback);
+
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ verify(mAnimatorCallback).onBackStarted(any());
}
-
@Test
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
- RemoteAnimationTarget animationTarget = createAnimationTarget();
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class));
+
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ verify(mAnimatorCallback).onBackStarted(any());
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
// In case the focus has been changed.
- IBinder token = mock(IBinder.class);
- mController.mFocusObserver.focusLost(token);
+ mController.mNavigationObserver.sendResult(null);
mShellExecutor.flushAll();
- verify(mIOnBackInvokedCallback).onBackCancelled();
+ verify(mAnimatorCallback).onBackCancelled();
// No more back invoke.
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void animationNotDefined() throws RemoteException {
+ final int[] testTypes = new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE};
+
+ for (int type: testTypes) {
+ unregisterAnimation(type);
+ }
+
+ for (int type: testTypes) {
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+ }
+
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(any());
+ verify(mAppCallback, times(testTypes.length)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void callbackShouldDeliverProgress() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+
+ verify(mAppCallback, times(1)).onBackStarted(any());
+ verify(mAppCallback, times(1)).onBackProgressed(any());
+ verify(mAppCallback, times(1)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void testBackToActivity() throws RemoteException {
+ final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ }
+
+ @Test
+ public void testBackToTask() throws RemoteException {
+ final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
+ mAnimationBackground);
+ verifySystemBackBehavior(
+ BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ }
+
+ private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
+ throws RemoteException {
+ final BackAnimationRunner animationRunner = spy(animation);
+ final IRemoteAnimationRunner runner = spy(animationRunner.getRunner());
+ final IOnBackInvokedCallback callback = spy(animationRunner.getCallback());
+
+ // Set up the monitoring objects.
+ doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(runner).when(animationRunner).getRunner();
+ doReturn(callback).when(animationRunner).getCallback();
+
+ mController.registerAnimation(type, animationRunner);
+
+ createNavigationInfo(type, true);
+
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+
+ // Check that back start and progress is dispatched when first move.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+ simulateRemoteAnimationStart(type);
+
+ verify(callback).onBackStarted(any(BackMotionEvent.class));
+ verify(animationRunner).startAnimation(any(), any(), any(), any());
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verify(callback).onBackInvoked();
}
private void doMotionEvent(int actionDown, int coordinate) {
@@ -373,16 +463,39 @@ public class BackAnimationControllerTest extends ShellTestCase {
coordinate, coordinate,
actionDown,
BackEvent.EDGE_LEFT);
- mEventTime += 10;
}
- private void simulateRemoteAnimationStart(int type, RemoteAnimationTarget animationTarget)
- throws RemoteException {
- if (mController.mIBackAnimationRunner != null) {
- final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
- mController.mIBackAnimationRunner.onAnimationStart(mIBackNaviAnimationController, type,
- targets, null, null);
+ private void simulateRemoteAnimationStart(int type) throws RemoteException {
+ RemoteAnimationTarget animationTarget = createAnimationTarget();
+ RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
+ if (mController.mBackAnimationAdapter != null) {
+ mController.mBackAnimationAdapter.getRunner().onAnimationStart(
+ targets, null, null, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
}
+
+ private void simulateRemoteAnimationFinished() {
+ mController.onBackAnimationFinished();
+ mController.finishBackNavigation();
+ }
+
+ private void registerAnimation(int type) {
+ mController.registerAnimation(type,
+ new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ }
+
+ private void unregisterAnimation(int type) {
+ mController.unregisterAnimation(type);
+ }
+
+ private static class ResultListener implements RemoteCallback.OnResultListener {
+ boolean mBackNavigationDone = false;
+ boolean mTriggerBack = false;
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mBackNavigationDone = true;
+ mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+ }
+ };
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
new file mode 100644
index 000000000000..3608474bd90e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 android.window.BackEvent.EDGE_LEFT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackProgressAnimator;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class BackProgressAnimatorTest {
+ private BackProgressAnimator mProgressAnimator;
+ private BackEvent mReceivedBackEvent;
+ private float mTargetProgress = 0.5f;
+ private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
+ private Handler mMainThreadHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ mMainThreadHandler = new Handler(Looper.getMainLooper());
+ final BackMotionEvent backEvent = new BackMotionEvent(
+ 0, 0,
+ 0, EDGE_LEFT, null);
+ mMainThreadHandler.post(
+ () -> {
+ mProgressAnimator = new BackProgressAnimator();
+ mProgressAnimator.onBackStarted(backEvent, this::onGestureProgress);
+ });
+ }
+
+ @Test
+ public void testBackProgressed() throws InterruptedException {
+ final BackMotionEvent backEvent = new BackMotionEvent(
+ 100, 0,
+ mTargetProgress, EDGE_LEFT, null);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+
+ assertNotNull(mReceivedBackEvent);
+ assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+ }
+
+ @Test
+ public void testBackCancelled() throws InterruptedException {
+ // Give the animator some progress.
+ final BackMotionEvent backEvent = new BackMotionEvent(
+ 100, 0,
+ mTargetProgress, EDGE_LEFT, null);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+
+ // Trigger animation cancel, the target progress should be 0.
+ mTargetProgress = 0;
+ mTargetProgressCalled = new CountDownLatch(1);
+ CountDownLatch cancelCallbackCalled = new CountDownLatch(1);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackCancelled(() -> cancelCallbackCalled.countDown()));
+ cancelCallbackCalled.await(1, TimeUnit.SECONDS);
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+ assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */);
+ }
+
+ private void onGestureProgress(BackEvent backEvent) {
+ if (mTargetProgress == backEvent.getProgress()) {
+ mReceivedBackEvent = backEvent;
+ mTargetProgressCalled.countDown();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
new file mode 100644
index 000000000000..2814ef9e26cc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.BackNavigationInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class CustomizeActivityAnimationTest extends ShellTestCase {
+ private static final int BOUND_SIZE = 100;
+ @Mock
+ private BackAnimationBackground mBackAnimationBackground;
+ @Mock
+ private Animation mMockCloseAnimation;
+ @Mock
+ private Animation mMockOpenAnimation;
+
+ private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
+ @Before
+ public void setUp() throws Exception {
+ mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
+ mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
+ mock(Choreographer.class));
+ spyOn(mCustomizeActivityAnimation);
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .load(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .load(any(), eq(true));
+ }
+
+ RemoteAnimationTarget createAnimationTarget(boolean open) {
+ SurfaceControl topWindowLeash = new SurfaceControl();
+ return new RemoteAnimationTarget(1,
+ open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
+ topWindowLeash, false, new Rect(), new Rect(), -1,
+ new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
+ new WindowConfiguration(), true, null, null, null, false, -1);
+ }
+
+ @Test
+ public void receiveFinishAfterInvoke() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishAfterCancel() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ final RemoteAnimationTarget close = createAnimationTarget(false);
+ final RemoteAnimationTarget open = createAnimationTarget(true);
+ // start animation with remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+ verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
+ eq(BOUND_SIZE), eq(BOUND_SIZE));
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ } catch (RemoteException r) {
+ fail("onBackCancelled throw remote exception");
+ }
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
+ mCustomizeActivityAnimation.prepareNextAnimation(
+ new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+ // start animation without any remote animation targets
+ final CountDownLatch finishCalled = new CountDownLatch(1);
+ final Runnable finishCallback = finishCalled::countDown;
+ mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
+ new RemoteAnimationTarget[]{}, null, null, finishCallback);
+
+ try {
+ mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ } catch (RemoteException r) {
+ fail("onBackInvoked throw remote exception");
+ }
+ verify(mCustomizeActivityAnimation).onGestureCommitted();
+ finishCalled.await(1, TimeUnit.SECONDS);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 262ee72d86fc..01e2f988fbfc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.common;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.ROTATION_0;
import static android.view.WindowInsets.Type.ime;
@@ -41,7 +41,6 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
-import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.sysui.ShellInit;
@@ -58,8 +57,6 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Mock
private SurfaceControl.Transaction mT;
@Mock
- private IInputMethodManager mMock;
- @Mock
private ShellInit mShellInit;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -79,10 +76,6 @@ public class DisplayImeControllerTest extends ShellTestCase {
}
}, mExecutor) {
@Override
- public IInputMethodManager getImms() {
- return mMock;
- }
- @Override
void removeImeSurface() { }
}.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
@@ -106,13 +99,13 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.showInsets(ime(), true);
+ mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.hideInsets(ime(), true);
+ mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
@@ -145,14 +138,15 @@ public class DisplayImeControllerTest extends ShellTestCase {
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
- ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE)
+ ID_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
+ Insets.NONE)
};
}
private InsetsState insetsStateWithIme(boolean visible) {
InsetsState state = new InsetsState();
- state.addSource(new InsetsSource(ITYPE_IME));
- state.setSourceVisible(ITYPE_IME, visible);
+ state.addSource(new InsetsSource(ID_IME, ime()));
+ state.setSourceVisible(ID_IME, visible);
return state;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 5f5a3c584ee0..956f1cd419c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.SparseArray;
@@ -32,7 +33,8 @@ import android.view.IDisplayWindowInsetsController;
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -108,11 +110,13 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -128,11 +132,13 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
assertTrue(secondListener.hideInsetsCount == 0);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -175,8 +181,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
int hideInsetsCount = 0;
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
topFocusedWindowChangedCount++;
}
@@ -192,12 +197,12 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
showInsetsCount++;
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
hideInsetsCount++;
}
}
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 98de58404e80..bc0d93a6810a 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
@@ -18,7 +18,7 @@ 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 android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -332,7 +332,8 @@ public class CompatUIControllerTest extends ShellTestCase {
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 insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
insetsSource.setFrame(0, 0, 1000, 1000);
insetsState.addSource(insetsSource);
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 0c5edc3f59de..4de529885565 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,7 +20,7 @@ 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 android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -337,8 +337,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Update if the insets change on the existing display layout
clearInvocations(mWindowManager);
InsetsState insetsState = new InsetsState();
- InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
- insetsSource.setFrame(0, 0, 1000, 1000);
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, 1800, 1000, 2000);
insetsState.addSource(insetsSource);
displayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager.updateDisplayLayout(displayLayout);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 48415d47304c..69f664a3a89d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -37,6 +37,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -94,8 +95,8 @@ public class FreeformTaskTransitionObserverTest {
public void testCreatesWindowDecorOnOpenTransition_freeform() {
final TransitionInfo.Change change =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- info.addChange(change);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -111,8 +112,8 @@ public class FreeformTaskTransitionObserverTest {
public void testPreparesWindowDecorOnCloseTransition_freeform() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
- info.addChange(change);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -128,8 +129,8 @@ public class FreeformTaskTransitionObserverTest {
public void testDoesntCloseWindowDecorDuringCloseTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
- info.addChange(change);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -144,8 +145,8 @@ public class FreeformTaskTransitionObserverTest {
public void testClosesWindowDecorAfterCloseTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
- info.addChange(change);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change).build();
final AutoCloseable windowDecor = mock(AutoCloseable.class);
@@ -164,8 +165,8 @@ public class FreeformTaskTransitionObserverTest {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfo(TRANSIT_OPEN, 0);
- info1.addChange(change1);
+ final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -176,8 +177,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
- info2.addChange(change2);
+ final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
@@ -195,8 +196,8 @@ public class FreeformTaskTransitionObserverTest {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
- info1.addChange(change1);
+ final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -207,8 +208,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
- info2.addChange(change2);
+ final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index ec264a643785..addc2338144f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -55,25 +55,28 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private static final float MAX_ASPECT_RATIO = 2f;
private static final int DEFAULT_MIN_EDGE_SIZE = 100;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
- private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
}
private void initializeMockResources() {
@@ -88,6 +91,9 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
R.dimen.default_minimal_size_pip_resizable_task,
DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+ res.addOverride(
R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
res.addOverride(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 341a451eeb43..f32000445ca9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -27,11 +27,13 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.util.Size;
import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
@@ -52,13 +54,23 @@ public class PipBoundsStateTest extends ShellTestCase {
private static final Size DEFAULT_SIZE = new Size(10, 10);
private static final float DEFAULT_SNAP_FRACTION = 1.0f;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsState mPipBoundsState;
private ComponentName mTestComponentName1;
private ComponentName mTestComponentName2;
@Before
public void setUp() {
- mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+
+ PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipBoundsState = new PipBoundsState(mContext,
+ new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e907cd3ca0ad..15bb10ed4f2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -88,6 +88,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -97,15 +98,16 @@ public class PipTaskOrganizerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
- mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
+ mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
@@ -259,8 +261,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final DisplayInfo info = new DisplayInfo();
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index a41a30e73baa..6995d10dd78d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -60,6 +60,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -109,6 +110,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController;
@@ -132,12 +134,13 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
- mMockTabletopModeController, mMockOneHandedController, mMockExecutor);
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
+ mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -223,12 +226,13 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
- mMockTabletopModeController, mMockOneHandedController, mMockExecutor));
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
+ mMockOneHandedController, mMockExecutor));
}
@Test
@@ -285,8 +289,8 @@ public class PipControllerTest extends ShellTestCase {
when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1));
when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE));
when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(true);
@@ -301,8 +305,8 @@ public class PipControllerTest extends ShellTestCase {
final int displayId = 1;
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(false);
@@ -317,7 +321,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.setEnablePipKeepClearAlgorithm(false);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
@@ -330,7 +334,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.setEnablePipKeepClearAlgorithm(true);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index c7b9eb3d1074..5b62a940c074 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,6 +37,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -87,11 +88,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index d9ff7d1f1089..390c830069eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -33,6 +33,7 @@ import android.view.DisplayInfo;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
import org.junit.Assert;
@@ -74,6 +75,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Mock private Context mContext;
@Mock private Resources mResources;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private PipSizeSpecHandler mPipSizeSpecHandler;
/**
@@ -137,7 +139,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Before
public void setUp() {
initExpectedSizes();
- setUpStaticSystemPropertiesSession();
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
@@ -148,11 +149,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
-
- // no overridden min edge size by default
- mPipSizeSpecHandler.setOverrideMinSize(null);
-
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
@@ -161,7 +157,14 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
false, false);
- mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+
+ setUpStaticSystemPropertiesSession();
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+
+ // no overridden min edge size by default
+ mPipSizeSpecHandler.setOverrideMinSize(null);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 1515d6057baf..10b1ddf1b868 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -35,6 +35,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -92,6 +93,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
@@ -105,8 +107,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
@@ -124,8 +127,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler);
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipBoundsState.setDisplayLayout(mDisplayLayout);
- mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
+ mPipDisplayLayoutState.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
new file mode 100644
index 000000000000..736d4cff6ce8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module TV pip owners
+galinap@google.com
+bronger@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
new file mode 100644
index 000000000000..e5b61ed2fd25
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -0,0 +1,328 @@
+/*
+ * 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 static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipMediaController;
+
+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.List;
+
+/**
+ * Unit tests for {@link TvPipActionsProvider}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TvPipActionProviderTest extends ShellTestCase {
+ private static final String TAG = TvPipActionProviderTest.class.getSimpleName();
+ private TvPipActionsProvider mActionsProvider;
+
+ @Mock
+ private PipMediaController mMockPipMediaController;
+ @Mock
+ private TvPipActionsProvider.Listener mMockListener;
+ @Mock
+ private TvPipAction.SystemActionsHandler mMockSystemActionsHandler;
+ @Mock
+ private Icon mMockIcon;
+ @Mock
+ private PendingIntent mMockPendingIntent;
+
+ private RemoteAction createRemoteAction(int identifier) {
+ return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
+ }
+
+ private List<RemoteAction> createRemoteActions(int numberOfActions) {
+ List<RemoteAction> actions = new ArrayList<>();
+ for (int i = 0; i < numberOfActions; i++) {
+ actions.add(createRemoteAction(i));
+ }
+ return actions;
+ }
+
+ private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
+ for (int i = 0; i < actions.size(); i++) {
+ int type = actions.get(i).getActionType();
+ if (type != actionTypes[i]) {
+ Log.e(TAG, "Action at index " + i + ": found " + type
+ + ", expected " + actionTypes[i]);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Before
+ public void setUp() {
+ if (!isTelevision()) {
+ return;
+ }
+ MockitoAnnotations.initMocks(this);
+ mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
+ mMockSystemActionsHandler);
+ }
+
+ @Test
+ public void defaultSystemActions_regularPip() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ }
+
+ @Test
+ public void defaultSystemActions_expandedPip() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(true);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_enable() {
+ assumeTelevision();
+ // PiP has expanded PiP disabled.
+ mActionsProvider.updateExpansionEnabled(false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_disable() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(false);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_AlreadyEnabled() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_toggleExpansion() {
+ assumeTelevision();
+ // PiP has expanded PiP enabled, but is in a collapsed state
+ mActionsProvider.updateExpansionEnabled(true);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(0, 1, 3);
+ }
+
+ @Test
+ public void customActions_added() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.addListener(mMockListener);
+
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedMore() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedLess() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseAdded() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = new ArrayList<>();
+ mActionsProvider.setAppActions(customActions, null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customClose_matchesOtherCustomAction() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ RemoteAction customClose = createRemoteAction(/* id= */ 10);
+ customActions.add(customClose);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, customClose);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void mediaActions_added_whileCustomActionsExist() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void customActions_removed_whileMediaActionsExist() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseOnly_mediaActionsShowing() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customActions_showDisabledActions() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.setAppActions(customActions, null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ }
+
+ @Test
+ public void mediaActions_hideDisabledActions() {
+ assumeTelevision();
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.onMediaActionsChanged(customActions);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+ }
+
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 05e472245b4a..7370ed71bbdd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -24,6 +24,7 @@ import android.os.test.TestLooper
import android.testing.AndroidTestingRunner
import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
@@ -43,7 +44,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
-class TvPipBoundsControllerTest {
+class TvPipBoundsControllerTest : ShellTestCase() {
val ANIMATION_DURATION = 100
val STASH_DURATION = 5000
val FAR_FUTURE = 60 * 60000L
@@ -71,7 +72,7 @@ class TvPipBoundsControllerTest {
var inMoveMode = false
@Mock
- lateinit var context: Context
+ lateinit var mockContext: Context
@Mock
lateinit var resources: Resources
@Mock
@@ -83,6 +84,9 @@ class TvPipBoundsControllerTest {
@Before
fun setUp() {
+ if (!isTelevision) {
+ return
+ }
MockitoAnnotations.initMocks(this)
time = 0L
inMenu = false
@@ -91,13 +95,13 @@ class TvPipBoundsControllerTest {
testLooper = TestLooper { time }
mainHandler = Handler(testLooper.getLooper())
- whenever(context.resources).thenReturn(resources)
+ whenever(mockContext.resources).thenReturn(resources)
whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION)
whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any()))
.then(returnsFirstArg<Rect>())
boundsController = TvPipBoundsController(
- context,
+ mockContext,
{ time },
mainHandler,
tvPipBoundsState,
@@ -107,6 +111,7 @@ class TvPipBoundsControllerTest {
@Test
fun testPlacement_MovedAfterDebounceTimeout() {
+ assumeTelevision()
triggerPlacement(MOVED_PLACEMENT)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS)
assertNoMovementUpTo(time + FAR_FUTURE)
@@ -114,6 +119,7 @@ class TvPipBoundsControllerTest {
@Test
fun testStashedPlacement_MovedAfterDebounceTimeout_Unstashes() {
+ assumeTelevision()
triggerPlacement(STASHED_PLACEMENT_RESTASH)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS)
@@ -121,6 +127,7 @@ class TvPipBoundsControllerTest {
@Test
fun testDebounceSamePlacement_MovesDebounceTimeoutAfterFirstPlacement() {
+ assumeTelevision()
triggerPlacement(MOVED_PLACEMENT)
advanceTimeTo(POSITION_DEBOUNCE_TIMEOUT_MILLIS / 2)
triggerPlacement(MOVED_PLACEMENT)
@@ -130,6 +137,7 @@ class TvPipBoundsControllerTest {
@Test
fun testNoMovementUntilPlacementStabilizes() {
+ assumeTelevision()
triggerPlacement(ANCHOR_PLACEMENT)
advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10)
triggerPlacement(MOVED_PLACEMENT)
@@ -143,6 +151,7 @@ class TvPipBoundsControllerTest {
@Test
fun testUnstashIfStashNoLongerNecessary() {
+ assumeTelevision()
triggerPlacement(STASHED_PLACEMENT_RESTASH)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
@@ -152,6 +161,7 @@ class TvPipBoundsControllerTest {
@Test
fun testRestashingPlacementDelaysUnstash() {
+ assumeTelevision()
triggerPlacement(STASHED_PLACEMENT_RESTASH)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
@@ -163,6 +173,7 @@ class TvPipBoundsControllerTest {
@Test
fun testNonRestashingPlacementDoesNotDelayUnstash() {
+ assumeTelevision()
triggerPlacement(STASHED_PLACEMENT_RESTASH)
assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS)
@@ -173,13 +184,26 @@ class TvPipBoundsControllerTest {
@Test
fun testImmediatePlacement() {
+ assumeTelevision()
triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH)
assertMovement(STASHED_BOUNDS)
assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS)
}
@Test
+ fun testImmediatePlacement_DoNotStashIfAlreadyUnstashed() {
+ assumeTelevision()
+ triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovement(STASHED_BOUNDS)
+ assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS)
+
+ triggerImmediatePlacement(STASHED_PLACEMENT)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ @Test
fun testInMoveMode_KeepAtAnchor() {
+ assumeTelevision()
startMoveMode()
triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
assertMovement(ANCHOR_BOUNDS)
@@ -188,6 +212,7 @@ class TvPipBoundsControllerTest {
@Test
fun testInMenu_Unstashed() {
+ assumeTelevision()
openPipMenu()
triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
assertMovement(MOVED_BOUNDS)
@@ -196,6 +221,7 @@ class TvPipBoundsControllerTest {
@Test
fun testCloseMenu_DoNotRestash() {
+ assumeTelevision()
openPipMenu()
triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
assertMovement(MOVED_BOUNDS)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
new file mode 100644
index 000000000000..f9b772345b14
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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 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 org.junit.Assert.assertEquals;
+
+import android.view.Gravity;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+
+public class TvPipGravityTest extends ShellTestCase {
+
+ private static final float VERTICAL_EXPANDED_ASPECT_RATIO = 1f / 3;
+ private static final float HORIZONTAL_EXPANDED_ASPECT_RATIO = 3f;
+
+ @Mock
+ private PipSnapAlgorithm mMockPipSnapAlgorithm;
+
+ private TvPipBoundsState mTvPipBoundsState;
+ private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
+ @Before
+ public void setUp() {
+ if (!isTelevision()) {
+ return;
+ }
+ MockitoAnnotations.initMocks(this);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+ mPipDisplayLayoutState);
+ mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
+ mMockPipSnapAlgorithm, mPipSizeSpecHandler);
+
+ setRTL(false);
+ }
+
+ private void checkGravity(int gravityActual, int gravityExpected) {
+ assertEquals(gravityExpected, gravityActual);
+ }
+
+ private void setRTL(boolean isRtl) {
+ mContext.getResources().getConfiguration().setLayoutDirection(
+ isRtl ? new Locale("ar") : Locale.ENGLISH);
+ mTvPipBoundsState.onConfigurationChanged();
+ mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ }
+
+ private void assertGravityAfterExpansion(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterCollapse(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterExpandAndCollapse(int gravityStartAndEnd) {
+ mTvPipBoundsState.setTvPipGravity(gravityStartAndEnd);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityStartAndEnd);
+ }
+
+ @Test
+ public void regularPip_defaultGravity() {
+ assumeTelevision();
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void regularPip_defaultGravity_RTL() {
+ assumeTelevision();
+ setRTL(true);
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void updateGravity_expand_vertical() {
+ assumeTelevision();
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_horizontal() {
+ assumeTelevision();
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void updateGravity_collapse() {
+ assumeTelevision();
+ // Vertical expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.LEFT);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_collapse_RTL() {
+ assumeTelevision();
+ setRTL(true);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_collapse() {
+ assumeTelevision();
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_expand_move_collapse() {
+ assumeTelevision();
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
+ Gravity.TOP | Gravity.LEFT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.BOTTOM | Gravity.LEFT, KEYCODE_DPAD_UP,
+ Gravity.TOP | Gravity.LEFT);
+ }
+
+ private void expandMoveCollapseCheck(int gravityFrom, int keycode, int gravityTo) {
+ // Expand
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ // Move
+ mTvPipBoundsAlgorithm.updateGravity(keycode);
+ // Collapse
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void moveAndCheckGravity(int keycode, int gravityEnd, boolean expectChange) {
+ assertEquals(expectChange, mTvPipBoundsAlgorithm.updateGravity(keycode));
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityEnd);
+ }
+
+ @Test
+ public void updateGravity_move_regular_valid() {
+ assumeTelevision();
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ // clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.RIGHT, true);
+ // anti-clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.RIGHT, true);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_valid() {
+ assumeTelevision();
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true);
+ }
+
+ @Test
+ public void updateGravity_move_regular_invalid() {
+ assumeTelevision();
+ int gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+
+ gravity = Gravity.BOTTOM | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_invalid() {
+ assumeTelevision();
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ }
+
+ @Test
+ public void previousCollapsedGravity_defaultValue() {
+ assumeTelevision();
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ }
+
+ @Test
+ public void previousCollapsedGravity_changes_on_RTL() {
+ assumeTelevision();
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.RIGHT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.LEFT);
+
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.LEFT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+}
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
index 0fcc5cf384c9..aedf65ddc269 100644
--- 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
@@ -21,23 +21,25 @@ 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.ShellTestCase
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
+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 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.assertFalse
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
-class TvPipKeepClearAlgorithmTest {
+class TvPipKeepClearAlgorithmTest : ShellTestCase() {
private val DEFAULT_PIP_SIZE = Size(384, 216)
- private val EXPANDED_WIDE_PIP_SIZE = Size(384*2, 216)
+ private val EXPANDED_WIDE_PIP_SIZE = Size(384 * 2, 216)
+ private val EXPANDED_TALL_PIP_SIZE = Size(384, 216 * 4)
private val DASHBOARD_WIDTH = 484
private val BOTTOM_SHEET_HEIGHT = 524
private val STASH_OFFSET = 64
@@ -54,6 +56,9 @@ class TvPipKeepClearAlgorithmTest {
@Before
fun setup() {
+ if (!isTelevision) {
+ return
+ }
movementBounds = Rect(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height)
movementBounds.inset(SCREEN_EDGE_INSET, SCREEN_EDGE_INSET)
@@ -73,72 +78,84 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun testAnchorPosition_BottomRight() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
testAnchorPosition()
}
@Test
fun testAnchorPosition_TopRight() {
+ assumeTelevision()
gravity = Gravity.TOP or Gravity.RIGHT
testAnchorPosition()
}
@Test
fun testAnchorPosition_TopLeft() {
+ assumeTelevision()
gravity = Gravity.TOP or Gravity.LEFT
testAnchorPosition()
}
@Test
fun testAnchorPosition_BottomLeft() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.LEFT
testAnchorPosition()
}
@Test
fun testAnchorPosition_Right() {
+ assumeTelevision()
gravity = Gravity.RIGHT
testAnchorPosition()
}
@Test
fun testAnchorPosition_Left() {
+ assumeTelevision()
gravity = Gravity.LEFT
testAnchorPosition()
}
@Test
fun testAnchorPosition_Top() {
+ assumeTelevision()
gravity = Gravity.TOP
testAnchorPosition()
}
@Test
fun testAnchorPosition_Bottom() {
+ assumeTelevision()
gravity = Gravity.BOTTOM
testAnchorPosition()
}
@Test
fun testAnchorPosition_TopCenterHorizontal() {
+ assumeTelevision()
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
testAnchorPosition()
}
@Test
fun testAnchorPosition_BottomCenterHorizontal() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
testAnchorPosition()
}
@Test
fun testAnchorPosition_RightCenterVertical() {
+ assumeTelevision()
gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
testAnchorPosition()
}
@Test
fun testAnchorPosition_LeftCenterVertical() {
+ assumeTelevision()
gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
testAnchorPosition()
}
@@ -152,6 +169,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_KeepClearNotObstructing_StayAtAnchor() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.LEFT)
@@ -166,6 +184,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_UnrestrictedRightSidebar_PushedLeft() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
@@ -180,6 +199,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorTopRight_UnrestrictedRightSidebar_PushedLeft() {
+ assumeTelevision()
gravity = Gravity.TOP or Gravity.RIGHT
val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
@@ -194,6 +214,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomLeft_UnrestrictedRightSidebar_StayAtAnchor() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.LEFT
val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
@@ -208,6 +229,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ assumeTelevision()
gravity = Gravity.BOTTOM
val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
@@ -222,6 +244,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun testExpanded_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() {
+ assumeTelevision()
pipSize = EXPANDED_WIDE_PIP_SIZE
gravity = Gravity.BOTTOM
@@ -237,6 +260,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_RestrictedSmallBottomBar_PushedUp() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(96)
@@ -252,6 +276,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_RestrictedBottomSheet_StashDownAtAnchor() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -269,6 +294,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_UnrestrictedBottomSheet_PushUp() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -284,6 +310,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_UnrestrictedBottomSheet_RestrictedSidebar_StashAboveBottomSheet() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -309,6 +336,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_AnchorBottomRight_UnrestrictedBottomSheet_UnrestrictedSidebar_PushUpLeft() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -331,6 +359,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_Stashed_UnstashBoundsBecomeUnobstructed_Unstashes() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -361,6 +390,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -392,6 +422,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_Stashed_UnstashBoundsObstructionChanges_UnstashTimeExtended() {
+ assumeTelevision()
gravity = Gravity.BOTTOM or Gravity.RIGHT
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
@@ -431,6 +462,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_ExpandedPiPHeightExceedsMovementBounds_AtAnchor() {
+ assumeTelevision()
gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height)
testAnchorPosition()
@@ -438,6 +470,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_ExpandedPiPHeightExceedsMovementBounds_BottomBar_StashedUp() {
+ assumeTelevision()
gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height)
val bottomBar = makeBottomBar(96)
@@ -453,6 +486,7 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_PipInsets() {
+ assumeTelevision()
val insets = Insets.of(-1, -2, -3, -4)
algorithm.setPipPermanentDecorInsets(insets)
@@ -485,6 +519,41 @@ class TvPipKeepClearAlgorithmTest {
testAnchorPositionWithInsets(insets)
}
+ @Test
+ fun test_AnchorRightExpandedPiP_UnrestrictedRightSidebar_PushedLeft() {
+ assumeTelevision()
+ pipSize = EXPANDED_TALL_PIP_SIZE
+ gravity = 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_AnchorRightExpandedPiP_RestrictedRightSidebar_StashedRight() {
+ assumeTelevision()
+ assumeTelevision()
+ pipSize = EXPANDED_TALL_PIP_SIZE
+ gravity = Gravity.RIGHT
+
+ val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT)
+ restrictedAreas.add(sidebar)
+
+ val expectedBounds = getExpectedAnchorBounds()
+ expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top)
+
+ val placement = getActualPlacement()
+ assertEquals(expectedBounds, placement.bounds)
+ assertEquals(STASH_TYPE_RIGHT, placement.stashType)
+ assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
+ }
+
private fun testAnchorPositionWithInsets(insets: Insets) {
var pipRect = Rect(0, 0, pipSize.width, pipSize.height)
pipRect.inset(insets)
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 652f9b38c88f..df78d92a90c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -64,6 +65,7 @@ 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.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -156,12 +158,10 @@ public class SplitTransitionTests extends ShellTestCase {
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);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
+ .addChange(TRANSIT_OPEN, newTask)
+ .addChange(TRANSIT_CHANGE, reparentTask)
+ .build();
mSideStage.onTaskAppeared(newTask, createMockSurface());
mMainStage.onTaskAppeared(reparentTask, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -216,12 +216,10 @@ public class SplitTransitionTests extends ShellTestCase {
assertFalse(containsSplitExit(result));
// simulate the transition
- TransitionInfo.Change openChange = createChange(TRANSIT_TO_FRONT, newTask);
- TransitionInfo.Change hideChange = createChange(TRANSIT_TO_BACK, mSideChild);
-
- TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
- info.addChange(openChange);
- info.addChange(hideChange);
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
+ .addChange(TRANSIT_TO_FRONT, newTask)
+ .addChange(TRANSIT_TO_BACK, mSideChild)
+ .build();
mSideStage.onTaskAppeared(newTask, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -237,12 +235,10 @@ public class SplitTransitionTests extends ShellTestCase {
assertNotNull(result);
assertFalse(containsSplitExit(result));
- TransitionInfo.Change showChange = createChange(TRANSIT_TO_FRONT, mSideChild);
- TransitionInfo.Change closeChange = createChange(TRANSIT_CLOSE, newTask);
-
- info = new TransitionInfo(TRANSIT_CLOSE, 0);
- info.addChange(showChange);
- info.addChange(closeChange);
+ info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(TRANSIT_TO_FRONT, mSideChild)
+ .addChange(TRANSIT_CLOSE, newTask)
+ .build();
mSideStage.onTaskVanished(newTask);
accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -254,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Test
@UiThreadTest
- public void testEnterRecents() {
+ public void testEnterRecentsAndCommit() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -263,69 +259,66 @@ public class SplitTransitionTests extends ShellTestCase {
.build();
// Create a request to bring home forward
- TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
- assertTrue(result.isEmpty());
+ // Don't handle recents opening
+ assertNull(result);
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
- // simulate the transition
- TransitionInfo.Change homeChange = createChange(TRANSIT_TO_FRONT, homeTask);
- 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(homeChange);
- info.addChange(mainChange);
- info.addChange(sideChange);
+ // simulate the start of recents transition
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
- mStageCoordinator.startAnimation(transition, info,
- mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class),
- mock(Transitions.TransitionFinishCallback.class));
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure it cleans-up if recents doesn't restore
+ WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+ mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mock(SurfaceControl.Transaction.class));
+ assertFalse(mStageCoordinator.isSplitScreenVisible());
}
@Test
@UiThreadTest
- public void testDismissFromBeingOccluded() {
+ public void testEnterRecentsAndRestore() {
enterSplit();
- ActivityManager.RunningTaskInfo normalTask = new TestRunningTaskInfoBuilder()
+ ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setActivityType(ACTIVITY_TYPE_HOME)
.build();
- // Create a request to bring a normal task forward
- TransitionRequestInfo request =
- new TransitionRequestInfo(TRANSIT_TO_FRONT, normalTask, null);
+ // Create a request to bring home forward
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
- assertTrue(containsSplitExit(result));
+ // Don't handle recents opening
+ assertNull(result);
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
- // simulate the transition
- TransitionInfo.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);
+ // simulate the start of recents transition
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
- mStageCoordinator.startAnimation(transition, info,
- mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class),
- mock(Transitions.TransitionFinishCallback.class));
- assertFalse(mStageCoordinator.isSplitScreenVisible());
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure we remain in split after recents restores.
+ WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+ restoreWCT.reorder(mMainChild.token, true /* toTop */);
+ restoreWCT.reorder(mSideChild.token, true /* toTop */);
+ // simulate the restoreWCT being applied:
+ mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+ mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+ mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
}
@Test
@@ -334,11 +327,10 @@ public class SplitTransitionTests extends ShellTestCase {
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);
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+ .addChange(TRANSIT_TO_BACK, mMainChild)
+ .addChange(TRANSIT_TO_BACK, mSideChild)
+ .build();
IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE);
@@ -356,12 +348,10 @@ public class SplitTransitionTests extends ShellTestCase {
enterSplit();
// simulate the transition
- TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild);
- TransitionInfo.Change sideChange = createChange(TRANSIT_CHANGE, mSideChild);
-
- TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
- info.addChange(mainChange);
- info.addChange(sideChange);
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+ .addChange(TRANSIT_TO_BACK, mMainChild)
+ .addChange(TRANSIT_CHANGE, mSideChild)
+ .build();
IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER,
STAGE_TYPE_SIDE);
@@ -385,18 +375,17 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
- assertTrue(containsSplitExit(result));
+ // Don't reparent tasks until the animation is complete.
+ assertFalse(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 mainChange = createChange(TRANSIT_CHANGE, mMainChild);
- TransitionInfo.Change sideChange = createChange(TRANSIT_CLOSE, mSideChild);
-
- TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
- info.addChange(mainChange);
- info.addChange(sideChange);
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(TRANSIT_CHANGE, mMainChild)
+ .addChange(TRANSIT_CLOSE, mSideChild)
+ .build();
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -408,13 +397,10 @@ public class SplitTransitionTests extends ShellTestCase {
}
private TransitionInfo createEnterPairInfo() {
- TransitionInfo.Change mainChange = createChange(TRANSIT_OPEN, mMainChild);
- TransitionInfo.Change sideChange = createChange(TRANSIT_OPEN, mSideChild);
-
- TransitionInfo info = new TransitionInfo(TRANSIT_SPLIT_SCREEN_PAIR_OPEN, 0);
- info.addChange(mainChange);
- info.addChange(sideChange);
- return info;
+ return new TransitionInfoBuilder(TRANSIT_SPLIT_SCREEN_PAIR_OPEN, 0)
+ .addChange(TRANSIT_OPEN, mMainChild)
+ .addChange(TRANSIT_OPEN, mSideChild)
+ .build();
}
private void enterSplit() {
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 0bb809d354dc..2e2e49e40030 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
@@ -134,6 +134,7 @@ public class StageCoordinatorTests extends ShellTestCase {
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
+ when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
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 5ee8bf3006a3..1a1bebd28aef 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
@@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests extends ShellTestCase {
private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@Mock
private ShellTaskOrganizer mTaskOrganizer;
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 e5ae2962e6e4..bf62acfc47a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -24,8 +24,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -56,11 +56,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
-import android.view.Display;
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
@@ -106,36 +104,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
- TestStartingSurfaceDrawer mStartingSurfaceDrawer;
-
- static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
- int mAddWindowForTask = 0;
-
- TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- IconProvider iconProvider, TransactionPool pool) {
- super(context, splashScreenExecutor, iconProvider, pool);
- }
-
- @Override
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params, int suggestType) {
- // listen for addView
- mAddWindowForTask = taskId;
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
- // Do not wait for background color
- return false;
- }
-
- @Override
- protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo,
- boolean immediately) {
- // listen for removeView
- if (mAddWindowForTask == removalInfo.taskId) {
- mAddWindowForTask = 0;
- }
- mStartingWindowRecords.remove(removalInfo.taskId);
- }
- }
+ StartingSurfaceDrawer mStartingSurfaceDrawer;
private static class TestContext extends TestableContext {
TestContext(Context context) {
@@ -165,44 +134,51 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor,
+ mIconProvider, mTransactionPool);
mStartingSurfaceDrawer = spy(
- new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+ new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
mTransactionPool));
+ spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator);
+ spyOn(mStartingSurfaceDrawer.mWindowRecords);
+ spyOn(mStartingSurfaceDrawer.mWindowlessRecords);
}
@Test
public void testAddSplashScreenSurface() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(),
eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
removalInfo.taskId = windowInfo.taskInfo.taskId;
mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
}
@Test
public void testFallbackDefaultTheme() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, 0);
+ createWindowInfo(taskId, 0, mBinder);
final int[] theme = new int[1];
doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
- .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+ .getSplashScreenTheme(eq(0), any());
assertNotEquals(theme[0], 0);
}
@@ -241,7 +217,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
+ createWindowInfo(taskId, android.R.style.Theme, mBinder);
TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
new Rect(0, 0, 0, 50), true /* hasImeSurface */);
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -249,7 +225,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibility */, any() /* outInputChannel */,
+ anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
@@ -270,7 +246,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
any())).thenReturn(mockSnapshotWindow);
// Simulate a task snapshot window created with IME snapshot shown.
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
waitHandlerIdle(mTestHandler);
// Verify the task snapshot with IME snapshot will be removed when received the real IME
@@ -278,27 +254,36 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
// makeTaskSnapshotWindow shall call removeWindowSynced before there add a new
// StartingWindowRecord for the task.
mStartingSurfaceDrawer.onImeDrawnOnTask(1);
- verify(mStartingSurfaceDrawer, times(2))
- .removeWindowSynced(any(), eq(true));
+ verify(mStartingSurfaceDrawer.mWindowRecords, times(2))
+ .removeWindow(any(), eq(true));
}
}
@Test
public void testClearAllWindows() {
final int taskId = 1;
- final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
- STARTING_WINDOW_TYPE_SPLASH_SCREEN);
- waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
- eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
+ mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+
+ }
+ });
+ mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
+ new StartingSurfaceDrawer.StartingWindowRecord() {
+ @Override
+ public void removeIfPossible(StartingWindowRemovalInfo info,
+ boolean immediately) {
+ }
+ });
mStartingSurfaceDrawer.clearAllWindows();
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true));
- assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
+ verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
+ verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0);
}
@Test
@@ -351,7 +336,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
longAppDuration, longAppDuration));
}
- private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
+ private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
@@ -360,6 +345,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.topActivityInfo = info;
taskInfo.taskId = taskId;
+ windowInfo.appToken = appToken;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
windowInfo.topOpaqueWindowInsetsState = new InsetsState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
deleted file mode 100644
index 3de50bb60470..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-
-import android.app.ActivityManager.TaskDescription;
-import android.content.ComponentName;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.view.InsetsState;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.window.TaskSnapshot;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link TaskSnapshotWindow}.
- *
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotWindowTest extends ShellTestCase {
-
- private TaskSnapshotWindow mWindow;
-
- private void setupSurface(int width, int height) {
- setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- new Rect(0, 0, width, height));
- }
-
- private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis,
- int windowFlags, Rect taskBounds) {
- // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
- // this behavior set the taskSize to be the same as the taskBounds width and height. The
- // taskBounds passed here are assumed to be the same task bounds as when the snapshot was
- // taken. We assume there is no aspect ratio mismatch between the screenshot and the
- // taskBounds
- assertEquals(width, taskBounds.width());
- assertEquals(height, taskBounds.height());
- Point taskSize = new Point(taskBounds.width(), taskBounds.height());
-
- final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets);
- mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test",
- createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
- 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
- taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- new InsetsState(), null /* clearWindow */, new TestShellExecutor());
- }
-
- private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
- Rect contentInsets) {
- final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
- 1, HardwareBuffer.USAGE_CPU_READ_RARELY);
- return new TaskSnapshot(
- System.currentTimeMillis(),
- new ComponentName("", ""), buffer,
- ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
- Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
- false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
- 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
- }
-
- private static TaskDescription createTaskDescription(int background, int statusBar,
- int navigationBar) {
- final TaskDescription td = new TaskDescription();
- td.setBackgroundColor(background);
- td.setStatusBarColor(statusBar);
- td.setNavigationBarColor(navigationBar);
- return td;
- }
-
- @Test
- public void fillEmptyBackground_fillHorizontally() {
- setupSurface(200, 100);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(200);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
- verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
- }
-
- @Test
- public void fillEmptyBackground_fillVertically() {
- setupSurface(100, 200);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
- verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
- }
-
- @Test
- public void fillEmptyBackground_fillBoth() {
- setupSurface(200, 200);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(200);
- when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
- verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
- verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
- }
-
- @Test
- public void fillEmptyBackground_dontFill_sameSize() {
- setupSurface(100, 100);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
- verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
- }
-
- @Test
- public void fillEmptyBackground_dontFill_bitmapLarger() {
- setupSurface(100, 100);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
- verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
- }
-
- @Test
- public void testCalculateSnapshotCrop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop());
- }
-
- @Test
- public void testCalculateSnapshotCrop_taskNotOnTop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150));
- assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop());
- }
-
- @Test
- public void testCalculateSnapshotCrop_navBarLeft() {
- setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop());
- }
-
- @Test
- public void testCalculateSnapshotCrop_navBarRight() {
- setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop());
- }
-
- @Test
- public void testCalculateSnapshotCrop_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop());
- }
-
- @Test
- public void testCalculateSnapshotFrame() {
- setupSurface(100, 100);
- final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- assertEquals(new Rect(0, 0, 100, 80),
- mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
- }
-
- @Test
- public void testCalculateSnapshotFrame_navBarLeft() {
- setupSurface(100, 100);
- final Rect insets = new Rect(10, 10, 0, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- assertEquals(new Rect(10, 0, 100, 90),
- mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
- }
-
- @Test
- public void testCalculateSnapshotFrame_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
- final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(5, 0, 95, 100), insets);
- assertEquals(new Rect(0, 0, 90, 90),
- mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
- }
-
- @Test
- public void testDrawStatusBarBackground() {
- setupSurface(100, 100);
- final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
- verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
- }
-
- @Test
- public void testDrawStatusBarBackground_nullFrame() {
- setupSurface(100, 100);
- final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, null);
- verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
- }
-
- @Test
- public void testDrawStatusBarBackground_nope() {
- setupSurface(100, 100);
- final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
- verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
- }
-
- @Test
- public void testDrawNavigationBarBackground() {
- final Rect insets = new Rect(0, 10, 0, 10);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
- verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
- }
-
- @Test
- public void testDrawNavigationBarBackground_left() {
- final Rect insets = new Rect(10, 10, 0, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
- verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
- }
-
- @Test
- public void testDrawNavigationBarBackground_right() {
- final Rect insets = new Rect(0, 10, 10, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
- final Canvas mockCanvas = mock(Canvas.class);
- when(mockCanvas.getWidth()).thenReturn(100);
- when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
- verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
- }
-}
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 595c3b4880df..44f1f0147b52 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
@@ -45,6 +45,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
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.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -61,6 +62,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -83,6 +85,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -97,6 +100,7 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.util.ArrayList;
+import java.util.function.Function;
/**
* Tests for the shell transitions.
@@ -119,8 +123,8 @@ public class ShellTransitionTests extends ShellTestCase {
@Before
public void setUp() {
- doAnswer(invocation -> invocation.getArguments()[1])
- .when(mOrganizer).startTransition(any(), any());
+ doAnswer(invocation -> new Binder())
+ .when(mOrganizer).startNewTransition(anyInt(), any());
}
@Test
@@ -561,6 +565,121 @@ public class ShellTransitionTests extends ShellTestCase {
assertEquals(0, mDefaultHandler.activeCount());
}
+
+ @Test
+ public void testTransitionMergingOnFinish() {
+ final Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ // The current transition.
+ final IBinder transitToken1 = new Binder();
+ requestStartTransition(transitions, transitToken1);
+ onTransitionReady(transitions, transitToken1);
+
+ // The next ready transition.
+ final IBinder transitToken2 = new Binder();
+ requestStartTransition(transitions, transitToken2);
+ onTransitionReady(transitions, transitToken2);
+
+ // The non-ready merge candidate.
+ final IBinder transitTokenNotReady = new Binder();
+ requestStartTransition(transitions, transitTokenNotReady);
+
+ mDefaultHandler.setSimulateMerge(true);
+ mDefaultHandler.mFinishes.get(0).onTransitionFinished(null /* wct */, null /* wctCB */);
+
+ // Make sure that the non-ready transition is not merged.
+ assertEquals(0, mDefaultHandler.mergeCount());
+ }
+
+ @Test
+ public void testInterleavedMerging() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ Function<Boolean, IBinder> startATransition = (doMerge) -> {
+ IBinder token = new Binder();
+ if (doMerge) {
+ mDefaultHandler.setShouldMerge(token);
+ }
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ return token;
+ };
+
+ IBinder transitToken1 = startATransition.apply(false);
+ // merge first one
+ IBinder transitToken2 = startATransition.apply(true);
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // don't merge next one
+ IBinder transitToken3 = startATransition.apply(false);
+ // make sure nothing happened (since it wasn't merged)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // make a mergable
+ IBinder transitToken4 = startATransition.apply(true);
+ // make sure nothing happened since there is a non-mergable pending.
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // Queue up another mergable
+ IBinder transitToken5 = startATransition.apply(true);
+
+ // Queue up a non-mergable
+ IBinder transitToken6 = startATransition.apply(false);
+
+ // Our active now looks like: [playing, merged]
+ // and ready queue: [non-mergable, mergable, mergable, non-mergable]
+ // finish the playing one
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ // Now we should have the non-mergable playing now with 2 merged:
+ // active: [playing, merged, merged] queue: [non-mergable]
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(2, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ }
+
+ @Test
+ public void testTransitionOrderMatchesCore() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ IBinder shellInit = transitions.startTransition(TRANSIT_CLOSE,
+ new WindowContainerTransaction(), null /* handler */);
+ // make sure we are testing the "New" API.
+ verify(mOrganizer, times(1)).startNewTransition(eq(TRANSIT_CLOSE), any());
+ // WMCore may not receive the new transition before requesting its own.
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
+
+ // At this point, WM is working on its transition (the shell-initialized one is still
+ // queued), so continue the transition lifecycle for that.
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ // At this point, if things are not working, we'd get an NPE due to attempting to merge
+ // into the shellInit transition which hasn't started yet.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
@Test
public void testShouldRotateSeamlessly() throws Exception {
final RunningTaskInfo taskInfo =
@@ -920,43 +1039,6 @@ public class ShellTransitionTests extends ShellTestCase {
verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
}
- class TransitionInfoBuilder {
- final TransitionInfo mInfo;
-
- TransitionInfoBuilder(@WindowManager.TransitionType int type) {
- this(type, 0 /* flags */);
- }
-
- TransitionInfoBuilder(@WindowManager.TransitionType int type,
- @WindowManager.TransitionFlags int flags) {
- mInfo = new TransitionInfo(type, flags);
- mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
- }
-
- TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
- RunningTaskInfo taskInfo) {
- final TransitionInfo.Change change =
- new TransitionInfo.Change(null /* token */, createMockSurface(true));
- change.setMode(mode);
- change.setTaskInfo(taskInfo);
- mInfo.addChange(change);
- return this;
- }
-
- TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
- return addChange(mode, null /* taskInfo */);
- }
-
- TransitionInfoBuilder addChange(TransitionInfo.Change change) {
- mInfo.addChange(change);
- return this;
- }
-
- TransitionInfo build() {
- return mInfo;
- }
- }
-
class ChangeBuilder {
final TransitionInfo.Change mChange;
@@ -998,6 +1080,7 @@ public class ShellTransitionTests extends ShellTestCase {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
+ final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1012,7 +1095,7 @@ public class ShellTransitionTests extends ShellTestCase {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!mSimulateMerge) return;
+ if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
@@ -1028,12 +1111,23 @@ public class ShellTransitionTests extends ShellTestCase {
mSimulateMerge = sim;
}
+ void setShouldMerge(IBinder toMerge) {
+ mShouldMerge.add(toMerge);
+ }
+
void finishAll() {
final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
}
+ mShouldMerge.clear();
+ }
+
+ void finishOne() {
+ Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+ mMerged.clear();
+ fin.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {
@@ -1045,6 +1139,21 @@ public class ShellTransitionTests extends ShellTestCase {
}
}
+ private static void requestStartTransition(Transitions transitions, IBinder token) {
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ }
+
+ private static void onTransitionReady(Transitions transitions, IBinder token) {
+ transitions.onTransitionReady(token, createTransitionInfo(),
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class));
+ }
+
+ private static TransitionInfo createTransitionInfo() {
+ return new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ }
+
private static SurfaceControl createMockSurface(boolean valid) {
SurfaceControl sc = mock(SurfaceControl.class);
doReturn(valid).when(sc).isValid();
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index c80fb188e70f..28bda72bccdd 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -33,6 +33,7 @@ license {
cc_defaults {
name: "libandroidfw_defaults",
+ cpp_std: "gnu++2b",
cflags: [
"-Werror",
"-Wunreachable-code",
@@ -54,12 +55,14 @@ cc_library {
host_supported: true,
srcs: [
"ApkAssets.cpp",
+ "ApkParsing.cpp",
"Asset.cpp",
"AssetDir.cpp",
"AssetManager.cpp",
"AssetManager2.cpp",
"AssetsProvider.cpp",
"AttributeResolution.cpp",
+ "BigBuffer.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
"Idmap.cpp",
@@ -69,9 +72,11 @@ cc_library {
"misc.cpp",
"ObbFile.cpp",
"PosixUtils.cpp",
+ "ResourceTimer.cpp",
"ResourceTypes.cpp",
"ResourceUtils.cpp",
"StreamingZipInflater.cpp",
+ "StringPool.cpp",
"TypeWrappers.cpp",
"Util.cpp",
"ZipFileRO.cpp",
@@ -156,11 +161,13 @@ cc_test {
// Actual tests.
"tests/ApkAssets_test.cpp",
+ "tests/ApkParsing_test.cpp",
"tests/AppAsLib_test.cpp",
"tests/Asset_test.cpp",
"tests/AssetManager2_test.cpp",
"tests/AttributeFinder_test.cpp",
"tests/AttributeResolution_test.cpp",
+ "tests/BigBuffer_test.cpp",
"tests/ByteBucketArray_test.cpp",
"tests/Config_test.cpp",
"tests/ConfigDescription_test.cpp",
@@ -169,10 +176,12 @@ cc_test {
"tests/Idmap_test.cpp",
"tests/LoadedArsc_test.cpp",
"tests/Locale_test.cpp",
+ "tests/ResourceTimer_test.cpp",
"tests/ResourceUtils_test.cpp",
"tests/ResTable_test.cpp",
"tests/Split_test.cpp",
"tests/StringPiece_test.cpp",
+ "tests/StringPool_test.cpp",
"tests/Theme_test.cpp",
"tests/TypeWrappers_test.cpp",
"tests/ZipUtils_test.cpp",
@@ -204,6 +213,8 @@ cc_test {
"tests/data/**/*.apk",
"tests/data/**/*.arsc",
"tests/data/**/*.idmap",
+ ":FrameworkResourcesSparseTestApp",
+ ":FrameworkResourcesNotSparseTestApp",
],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 2beb33abe782..15aaae25f754 100755..100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
#include "android-base/errors.h"
#include "android-base/logging.h"
+#include "android-base/utf8.h"
namespace android {
@@ -83,15 +84,16 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
return {};
}
+ std::string overlay_path(loaded_idmap->OverlayApkPath());
+ auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
std::unique_ptr<AssetsProvider> overlay_assets;
- const std::string overlay_path(loaded_idmap->OverlayApkPath());
- if (IsFabricatedOverlay(overlay_path)) {
+ if (IsFabricatedOverlay(fd)) {
// Fabricated overlays do not contain resource definitions. All of the overlay resource values
// are defined inline in the idmap.
- overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+ overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
} else {
// The overlay should be an APK.
- overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+ overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
}
if (overlay_assets == nullptr) {
return {};
@@ -141,6 +143,9 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_
return {};
}
loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+ } else if (loaded_idmap != nullptr &&
+ IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+ loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
} else {
loaded_arsc = LoadedArsc::CreateEmpty();
}
diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp
new file mode 100644
index 000000000000..32d2c5b05acb
--- /dev/null
+++ b/libs/androidfw/ApkParsing.cpp
@@ -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.
+ */
+
+#include "androidfw/ApkParsing.h"
+#include <algorithm>
+#include <array>
+#include <stdlib.h>
+#include <string_view>
+#include <sys/types.h>
+
+const std::string_view APK_LIB = "lib/";
+const size_t APK_LIB_LEN = APK_LIB.size();
+
+const std::string_view LIB_PREFIX = "/lib";
+const size_t LIB_PREFIX_LEN = LIB_PREFIX.size();
+
+const std::string_view LIB_SUFFIX = ".so";
+const size_t LIB_SUFFIX_LEN = LIB_SUFFIX.size();
+
+static const std::array<std::string_view, 2> abis = {"arm64-v8a", "x86_64"};
+
+namespace android::util {
+const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, bool debuggable) {
+ // Make sure the filename is at least to the minimum library name size.
+ const size_t fileNameLen = strlen(fileName);
+ static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN;
+ if (fileNameLen < minLength) {
+ return nullptr;
+ }
+
+ const char* lastSlash = strrchr(fileName, '/');
+ if (!lastSlash) {
+ return nullptr;
+ }
+
+ // Skip directories.
+ if (*(lastSlash + 1) == 0) {
+ return nullptr;
+ }
+
+ // Make sure the filename is safe.
+ if (!isFilenameSafe(lastSlash + 1)) {
+ return nullptr;
+ }
+
+ // Make sure there aren't subdirectories by checking if the next / after lib/ is the last slash
+ if (memchr(fileName + APK_LIB_LEN, '/', fileNameLen - APK_LIB_LEN) != lastSlash) {
+ return nullptr;
+ }
+
+ if (!debuggable) {
+ // Make sure the filename starts with lib and ends with ".so".
+ if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX.data(), LIB_SUFFIX_LEN) != 0
+ || strncmp(lastSlash, LIB_PREFIX.data(), LIB_PREFIX_LEN) != 0) {
+ return nullptr;
+ }
+ }
+
+ // Don't include 64 bit versions if they are suppressed
+ if (suppress64Bit && std::find(abis.begin(), abis.end(), std::string_view(
+ fileName + APK_LIB_LEN, lastSlash - fileName - APK_LIB_LEN)) != abis.end()) {
+ return nullptr;
+ }
+
+ return lastSlash;
+}
+
+bool isFilenameSafe(const char* filename) {
+ off_t offset = 0;
+ for (;;) {
+ switch (*(filename + offset)) {
+ case 0:
+ // Null.
+ // If we've reached the end, all the other characters are good.
+ return true;
+
+ case 'A' ... 'Z':
+ case 'a' ... 'z':
+ case '0' ... '9':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '/':
+ case '=':
+ case '_':
+ offset++;
+ break;
+
+ default:
+ // We found something that is not good.
+ return false;
+ }
+ }
+ // Should not reach here.
+}
+} \ No newline at end of file
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 400829e15364..68f5e4a88c7e 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -22,6 +22,7 @@
#include <iterator>
#include <map>
#include <set>
+#include <span>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -43,28 +44,19 @@ namespace {
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
- const uint16_t entry_size = dtohs(table_entry->size);
+ const uint16_t entry_size = table_entry->size();
// Check if the entry represents a bag value.
- if (entry_size >= sizeof(ResTable_map_entry) &&
- (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
- const auto map_entry = table_entry.convert<ResTable_map_entry>();
- if (!map_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- return map_entry.verified();
+ if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+ return table_entry.convert<ResTable_map_entry>().verified();
}
- // The entry represents a non-bag value.
- const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
- if (!entry_value) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- Res_value value;
- value.copyFrom_dtoh(entry_value.value());
- return value;
+ return table_entry->value();
}
} // namespace
@@ -120,7 +112,7 @@ void AssetManager2::BuildDynamicRefTable() {
// A mapping from path of apk assets that could be target packages of overlays to the runtime
// package id of its first loaded package. Overlays currently can only override resources in the
// first package in the target resource table.
- std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+ std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
// Overlay resources are not directly referenced by an application so their resource ids
// can change throughout the application's lifetime. Assign overlay package ids last.
@@ -143,7 +135,7 @@ void AssetManager2::BuildDynamicRefTable() {
if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
// The target package must precede the overlay package in the apk assets paths in order
// to take effect.
- auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+ auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
if (iter == target_assets_package_ids.end()) {
LOG(INFO) << "failed to find target package for overlay "
<< loaded_idmap->OverlayApkPath();
@@ -188,7 +180,7 @@ void AssetManager2::BuildDynamicRefTable() {
if (overlay_ref_table != nullptr) {
// If this package is from an overlay, use a dynamic reference table that can rewrite
// overlay resource ids to their corresponding target resource ids.
- new_group.dynamic_ref_table = overlay_ref_table;
+ new_group.dynamic_ref_table = std::move(overlay_ref_table);
}
DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -196,9 +188,9 @@ void AssetManager2::BuildDynamicRefTable() {
ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
- // Add the package and to the set of packages with the same ID.
+ // Add the package to the set of packages with the same ID.
PackageGroup* package_group = &package_groups_[idx];
- package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+ package_group->packages_.emplace_back().loaded_package_ = package.get();
package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
// Add the package name -> build time ID mappings.
@@ -210,30 +202,39 @@ void AssetManager2::BuildDynamicRefTable() {
if (auto apk_assets_path = apk_assets->GetPath()) {
// Overlay target ApkAssets must have been created using path based load apis.
- target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+ target_assets_package_ids.emplace(*apk_assets_path, package_id);
}
}
}
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
- const auto package_groups_end = package_groups_.end();
- for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
- const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
- for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
- iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
- iter->dynamic_ref_table->mAssignedPackageId);
-
- // Add the alias resources to the dynamic reference table of every package group. Since
- // staging aliases can only be defined by the framework package (which is not a shared
- // library), the compile-time package id of the framework is the same across all packages
- // that compile against the framework.
- for (const auto& package : iter->packages_) {
- for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
- iter2->dynamic_ref_table->addAlias(entry.first, entry.second);
- }
- }
+ DynamicRefTable::AliasMap aliases;
+ for (const auto& group : package_groups_) {
+ const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
+ const auto name_16 = String16(package_name.c_str(), package_name.size());
+ for (auto&& inner_group : package_groups_) {
+ inner_group.dynamic_ref_table->addMapping(name_16,
+ group.dynamic_ref_table->mAssignedPackageId);
+ }
+
+ for (const auto& package : group.packages_) {
+ const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
+ aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
}
}
+
+ if (!aliases.empty()) {
+ std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
+ // Add the alias resources to the dynamic reference table of every package group. Since
+ // staging aliases can only be defined by the framework package (which is not a shared
+ // library), the compile-time package id of the framework is the same across all packages
+ // that compile against the framework.
+ for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
+ group.dynamic_ref_table->setAliases(aliases);
+ }
+ package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
+ }
}
void AssetManager2::DumpToLog() const {
@@ -326,7 +327,7 @@ const std::unordered_map<std::string, std::string>*
return &loaded_package->GetOverlayableMap();
}
-bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
std::string* out) const {
uint8_t package_id = 0U;
for (const auto& apk_assets : apk_assets_) {
@@ -373,7 +374,7 @@ bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_
const std::string name = ToFormattedResourceString(*res_name);
output.append(base::StringPrintf(
"resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
- name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+ name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
}
}
}
@@ -501,7 +502,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con
continue;
}
- auto func = [&](const StringPiece& name, FileType type) {
+ auto func = [&](StringPiece name, FileType type) {
AssetDir::FileInfo info;
info.setFileName(String8(name.data(), name.size()));
info.setFileType(type);
@@ -611,7 +612,21 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
}
if (overlay_entry.IsInlineValue()) {
// The target resource is overlaid by an inline value not represented by a resource.
- result->entry = overlay_entry.GetInlineValue();
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
+ }
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
result->cookie = id_map.cookie;
@@ -800,17 +815,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
return base::unexpected(std::nullopt);
}
- auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
- if (!best_entry_result.has_value()) {
- return base::unexpected(best_entry_result.error());
- }
-
- const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
- if (!best_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
+ auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (!best_entry_verified.has_value()) {
+ return base::unexpected(best_entry_verified.error());
}
- const auto entry = GetEntryValue(best_entry.verified());
+ const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
@@ -823,7 +833,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
- best_entry->key.index),
+ (*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
@@ -1054,7 +1064,7 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const {
std::vector<uint32_t> found_resids;
const auto bag = GetBag(resid, found_resids);
- cached_bag_resid_stacks_.emplace(resid, found_resids);
+ cached_bag_resid_stacks_.emplace(resid, std::move(found_resids));
return bag;
}
@@ -1271,7 +1281,7 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(
return result;
}
-static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+static bool Utf8ToUtf16(StringPiece str, std::u16string* out) {
ssize_t len =
utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
if (len < 0) {
@@ -1346,22 +1356,22 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId(
void AssetManager2::RebuildFilterList() {
for (PackageGroup& group : package_groups_) {
- for (ConfiguredPackage& impl : group.packages_) {
- // Destroy it.
- impl.filtered_configs_.~ByteBucketArray();
-
- // Re-create it.
- new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
-
+ for (ConfiguredPackage& package : group.packages_) {
+ package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
// Create the filters here.
- impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
- FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+ package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+ FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
if (type_entry.config.match(configuration_)) {
- group.type_entries.push_back(&type_entry);
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
}
}
});
+ package.filtered_configs_.trimBuckets(
+ [](const auto& fcg) { return fcg.type_entries.empty(); });
}
}
}
@@ -1402,30 +1412,34 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const
std::unique_ptr<Theme> AssetManager2::NewTheme() {
constexpr size_t kInitialReserveSize = 32;
auto theme = std::unique_ptr<Theme>(new Theme(this));
+ theme->keys_.reserve(kInitialReserveSize);
theme->entries_.reserve(kInitialReserveSize);
return theme;
}
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags) const {
+ for (const PackageGroup& package_group : package_groups_) {
+ const auto loaded_package = package_group.packages_.front().loaded_package_;
+ if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+ && !func(loaded_package->GetPackageName(),
+ package_group.dynamic_ref_table->mAssignedPackageId)) {
+ return;
+ }
+ }
+}
+
Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
}
Theme::~Theme() = default;
struct Theme::Entry {
- uint32_t attr_res_id;
ApkAssetsCookie cookie;
uint32_t type_spec_flags;
Res_value value;
};
-namespace {
-struct ThemeEntryKeyComparer {
- bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
- return entry.attr_res_id < attr_res_id;
- }
-};
-} // namespace
-
base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_NAME("Theme::ApplyStyle");
@@ -1454,19 +1468,20 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid,
continue;
}
- Theme::Entry new_entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
- ThemeEntryKeyComparer{});
- if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+ if (key_it != keys_.end() && *key_it == attr_res_id) {
if (is_undefined) {
// DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
- /// true.
+ // true.
+ keys_.erase(key_it);
entries_.erase(entry_it);
} else if (force) {
- *entry_it = new_entry;
+ *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
}
} else {
- entries_.insert(entry_it, new_entry);
+ keys_.insert(key_it, attr_res_id);
+ entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
}
}
return {};
@@ -1477,6 +1492,7 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t*
ATRACE_NAME("Theme::Rebase");
// Reset the entries without changing the vector capacity to prevent reallocations during
// ApplyStyle.
+ keys_.clear();
entries_.clear();
asset_manager_ = am;
for (size_t i = 0; i < style_count; i++) {
@@ -1485,16 +1501,14 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t*
}
std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
constexpr const uint32_t kMaxIterations = 20;
uint32_t type_spec_flags = 0u;
for (uint32_t i = 0; i <= kMaxIterations; i++) {
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
- ThemeEntryKeyComparer{});
- if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+ if (key_it == keys_.end() || *key_it != resid) {
return std::nullopt;
}
-
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
type_spec_flags |= entry_it->type_spec_flags;
if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
resid = entry_it->value.data;
@@ -1528,6 +1542,7 @@ base::expected<std::monostate, NullOrIOError> Theme::ResolveAttributeReference(
}
void Theme::Clear() {
+ keys_.clear();
entries_.clear();
}
@@ -1539,18 +1554,19 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
type_spec_flags_ = source.type_spec_flags_;
if (asset_manager_ == source.asset_manager_) {
+ keys_ = source.keys_;
entries_ = source.entries_;
} else {
- std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
- typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
- std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+ std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+ using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+ std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
// Determine which ApkAssets are loaded in both theme AssetManagers.
- const auto src_assets = source.asset_manager_->GetApkAssets();
+ const auto& src_assets = source.asset_manager_->GetApkAssets();
for (size_t i = 0; i < src_assets.size(); i++) {
const ApkAssets* src_asset = src_assets[i];
- const auto dest_assets = asset_manager_->GetApkAssets();
+ const auto& dest_assets = asset_manager_->GetApkAssets();
for (size_t j = 0; j < dest_assets.size(); j++) {
const ApkAssets* dest_asset = dest_assets[j];
if (src_asset != dest_asset) {
@@ -1571,15 +1587,17 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
}
src_to_dest_asset_cookies.insert(std::make_pair(i, j));
- src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+ src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
break;
}
}
// Reset the data in the destination theme.
+ keys_.clear();
entries_.clear();
- for (const auto& entry : source.entries_) {
+ for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+ const auto& entry = source.entries_[i];
bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
|| entry.value.dataType == Res_value::TYPE_REFERENCE
|| entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1619,13 +1637,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
}
}
+ const auto source_res_id = source.keys_[i];
+
// The package id of the attribute needs to be rewritten to the package id of the
// attribute in the destination.
- int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+ int attribute_dest_package_id = get_package_id(source_res_id);
if (attribute_dest_package_id != 0x01) {
// Find the cookie of the attribute resource id in the source AssetManager
base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
- source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+ source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
true /* stop_at_first_match */,
true /* ignore_configuration */);
if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1649,16 +1669,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
attribute_dest_package_id = attribute_dest_package->second;
}
- auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
- get_entry_id(entry.attr_res_id));
- Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
- Res_value{.dataType = entry.value.dataType,
- .data = attribute_data}};
-
+ auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+ get_entry_id(source_res_id));
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
// Since the entries were cleared, the attribute resource id has yet been mapped to any value.
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
- ThemeEntryKeyComparer{});
- entries_.insert(entry_it, new_entry);
+ keys_.insert(key_it, dest_attr_id);
+ entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+ Res_value{.dataType = entry.value.dataType,
+ .data = attribute_data}});
}
}
return {};
@@ -1666,9 +1685,11 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) {
void Theme::Dump() const {
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
- for (auto& entry : entries_) {
+ for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+ auto res_id = keys_[i];
+ const auto& entry = entries_[i];
LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
- entry.attr_res_id, entry.value.data, entry.value.dataType,
+ res_id, entry.value.data, entry.value.dataType,
entry.cookie);
}
}
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index bce34d37c90b..2d3c06506a1f 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -73,9 +73,6 @@ std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd,
(path != nullptr) ? base::unique_fd(-1) : std::move(fd));
}
-ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
- : value_(std::forward<std::string>(value)), is_path_(is_path) {}
-
const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
return is_path_ ? &value_ : nullptr;
}
@@ -84,34 +81,42 @@ const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
return value_;
}
+void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
+ ::CloseArchive(a);
+}
+
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
package_property_t flags, time_t last_mod_time)
- : zip_handle_(handle, ::CloseArchive),
- name_(std::forward<PathOrDebugName>(path)),
+ : zip_handle_(handle),
+ name_(std::move(path)),
flags_(flags),
last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
- package_property_t flags) {
+ package_property_t flags,
+ base::unique_fd fd) {
+ const auto released_fd = fd.ok() ? fd.release() : -1;
ZipArchiveHandle handle;
- if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+ if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+ : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
struct stat sb{.st_mtime = -1};
- if (stat(path.c_str(), &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to stat file '" << path << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(path.c_str())) {
+ if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ PLOG(WARNING) << "Failed to stat file '" << path << "'";
+ }
}
return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
- true /* is_path */}, flags, sb.st_mtime));
+ new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -133,17 +138,19 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
}
struct stat sb{.st_mtime = -1};
- if (fstat(released_fd, &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(released_fd)) {
+ if (fstat(released_fd, &sb) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to fstat file '" << friendly_name
+ << "': " << base::SystemErrorCodeToString(errno);
+ }
}
- return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
- false /* is_path */}, flags, sb.st_mtime));
+ return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
+ handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -210,9 +217,9 @@ std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
return asset;
}
-bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f)
- const {
+bool ZipAssetsProvider::ForEachFile(
+ const std::string& root_path,
+ base::function_ref<void(StringPiece, FileType)> f) const {
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
root_path_full += '/';
@@ -238,8 +245,7 @@ bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
if (!leaf_file_path.empty()) {
auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
if (iter != leaf_file_path.end()) {
- std::string dir =
- leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+ std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)));
dirs.insert(std::move(dir));
} else {
f(leaf_file_path, kFileTypeRegular);
@@ -277,6 +283,9 @@ const std::string& ZipAssetsProvider::GetDebugName() const {
}
bool ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
struct stat sb{};
if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
// If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -287,10 +296,10 @@ bool ZipAssetsProvider::IsUpToDate() const {
}
DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
- : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
+ : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
- struct stat sb{};
+ struct stat sb;
const int result = stat(path.c_str(), &sb);
if (result == -1) {
LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -302,12 +311,13 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st
return nullptr;
}
- if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
+ if (path.back() != OS_PATH_SEPARATOR) {
path += OS_PATH_SEPARATOR;
}
- return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
- sb.st_mtime));
+ const bool isReadonly = isReadonlyFilesystem(path.c_str());
+ return std::unique_ptr<DirectoryAssetsProvider>(
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -324,8 +334,7 @@ std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string&
bool DirectoryAssetsProvider::ForEachFile(
const std::string& /* root_path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */)
- const {
+ base::function_ref<void(StringPiece, FileType)> /* f */) const {
return true;
}
@@ -338,7 +347,10 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const {
}
bool DirectoryAssetsProvider::IsUpToDate() const {
- struct stat sb{};
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb;
if (stat(dir_.c_str(), &sb) < 0) {
// If stat fails on the zip archive, return true so the zip archive the resource system does
// attempt to refresh the ApkAsset.
@@ -349,8 +361,7 @@ bool DirectoryAssetsProvider::IsUpToDate() const {
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
std::unique_ptr<AssetsProvider>&& secondary)
- : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
- secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
+ : primary_(std::move(primary)), secondary_(std::move(secondary)) {
debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath()
: secondary_->GetPath();
@@ -372,9 +383,9 @@ std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path
return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
}
-bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f)
- const {
+bool MultiAssetsProvider::ForEachFile(
+ const std::string& root_path,
+ base::function_ref<void(StringPiece, FileType)> f) const {
return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
}
@@ -397,8 +408,8 @@ std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() {
return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
}
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
- return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+ return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
}
std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
@@ -412,7 +423,7 @@ std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* p
bool EmptyAssetsProvider::ForEachFile(
const std::string& /* root_path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+ base::function_ref<void(StringPiece, FileType)> /* f */) const {
return true;
}
@@ -435,4 +446,4 @@ bool EmptyAssetsProvider::IsUpToDate() const {
return true;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/androidfw/BigBuffer.cpp b/libs/androidfw/BigBuffer.cpp
new file mode 100644
index 000000000000..bedfc49a1b0d
--- /dev/null
+++ b/libs/androidfw/BigBuffer.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <androidfw/BigBuffer.h>
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include "android-base/logging.h"
+
+namespace android {
+
+void* BigBuffer::NextBlockImpl(size_t size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.block_size_ - block.size >= size) {
+ void* out_buffer = block.buffer.get() + block.size;
+ block.size += size;
+ size_ += size;
+ return out_buffer;
+ }
+ }
+
+ const size_t actual_size = std::max(block_size_, size);
+
+ Block block = {};
+
+ // Zero-allocate the block's buffer.
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]());
+ CHECK(block.buffer);
+
+ block.size = size;
+ block.block_size_ = actual_size;
+
+ blocks_.push_back(std::move(block));
+ size_ += size;
+ return blocks_.back().buffer.get();
+}
+
+void* BigBuffer::NextBlock(size_t* out_size) {
+ if (!blocks_.empty()) {
+ Block& block = blocks_.back();
+ if (block.size != block.block_size_) {
+ void* out_buffer = block.buffer.get() + block.size;
+ size_t size = block.block_size_ - block.size;
+ block.size = block.block_size_;
+ size_ += size;
+ *out_size = size;
+ return out_buffer;
+ }
+ }
+
+ // Zero-allocate the block's buffer.
+ Block block = {};
+ block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]());
+ CHECK(block.buffer);
+ block.size = block_size_;
+ block.block_size_ = block_size_;
+ blocks_.push_back(std::move(block));
+ size_ += block_size_;
+ *out_size = block_size_;
+ return blocks_.back().buffer.get();
+}
+
+std::string BigBuffer::to_string() const {
+ std::string result;
+ for (const Block& block : blocks_) {
+ result.append(block.buffer.get(), block.buffer.get() + block.size);
+ }
+ return result;
+}
+
+} // namespace android
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 19ead9583eb2..cf2fd6f59b87 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -21,6 +21,7 @@
#include "androidfw/Util.h"
#include <string>
+#include <string_view>
#include <vector>
namespace android {
@@ -38,11 +39,11 @@ static bool parseMcc(const char* name, ResTable_config* out) {
return true;
}
const char* c = name;
- if (tolower(*c) != 'm') return false;
+ if (*c != 'm') return false;
c++;
- if (tolower(*c) != 'c') return false;
+ if (*c != 'c') return false;
c++;
- if (tolower(*c) != 'c') return false;
+ if (*c != 'c') return false;
c++;
const char* val = c;
@@ -68,11 +69,11 @@ static bool parseMnc(const char* name, ResTable_config* out) {
return true;
}
const char* c = name;
- if (tolower(*c) != 'm') return false;
+ if (*c != 'm') return false;
c++;
- if (tolower(*c) != 'n') return false;
+ if (*c != 'n') return false;
c++;
- if (tolower(*c) != 'c') return false;
+ if (*c != 'c') return false;
c++;
const char* val = c;
@@ -93,6 +94,23 @@ static bool parseMnc(const char* name, ResTable_config* out) {
return true;
}
+static bool parseGrammaticalInflection(const std::string& name, ResTable_config* out) {
+ using namespace std::literals;
+ if (name == "feminine"sv) {
+ if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_FEMININE;
+ return true;
+ }
+ if (name == "masculine"sv) {
+ if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_MASCULINE;
+ return true;
+ }
+ if (name == "neuter"sv) {
+ if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_NEUTER;
+ return true;
+ }
+ return false;
+}
+
static bool parseLayoutDirection(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out)
@@ -637,7 +655,7 @@ static bool parseVersion(const char* name, ResTable_config* out) {
return true;
}
-bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) {
std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
ConfigDescription config;
@@ -678,6 +696,13 @@ bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
}
}
+ if (parseGrammaticalInflection(*part_iter, &config)) {
+ ++part_iter;
+ if (part_iter == parts_end) {
+ goto success;
+ }
+ }
+
if (parseLayoutDirection(part_iter->c_str(), &config)) {
++part_iter;
if (part_iter == parts_end) {
@@ -832,11 +857,13 @@ success:
void ConfigDescription::ApplyVersionForCompatibility(
ConfigDescription* config) {
uint16_t min_sdk = 0;
- if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+ if (config->grammaticalInflection != 0) {
+ min_sdk = SDK_U;
+ } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
== ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
config->colorMode & ResTable_config::MASK_HDR) {
- min_sdk = SDK_O;
+ min_sdk = SDK_O;
} else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
min_sdk = SDK_MARSHMALLOW;
} else if (config->density == ResTable_config::DENSITY_ANY) {
@@ -913,6 +940,7 @@ bool ConfigDescription::HasHigherPrecedenceThan(
if (country[0] || o.country[0]) return (!o.country[0]);
// Script and variant require either a language or country, both of which
// have higher precedence.
+ if (grammaticalInflection || o.grammaticalInflection) return !o.grammaticalInflection;
if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
return !(o.screenLayout & MASK_LAYOUTDIR);
}
@@ -971,6 +999,7 @@ bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {
// The values here can be found in ResTable_config#match. Density and range
// values can't lead to conflicts, and are ignored.
return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) ||
+ !pred(grammaticalInflection, o.grammaticalInflection) ||
!pred(screenLayout & MASK_LAYOUTDIR,
o.screenLayout & MASK_LAYOUTDIR) ||
!pred(screenLayout & MASK_SCREENLONG,
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index efd1f6a25786..89835742c8ff 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -56,6 +56,8 @@ struct Idmap_header {
struct Idmap_data_header {
uint32_t target_entry_count;
uint32_t target_inline_entry_count;
+ uint32_t target_inline_entry_value_count;
+ uint32_t configuration_count;
uint32_t overlay_entry_count;
uint32_t string_pool_index_offset;
@@ -68,6 +70,12 @@ struct Idmap_target_entry {
struct Idmap_target_entry_inline {
uint32_t target_id;
+ uint32_t start_value_index;
+ uint32_t value_count;
+};
+
+struct Idmap_target_entry_inline_value {
+ uint32_t config_index;
Res_value value;
};
@@ -138,11 +146,15 @@ status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) cons
IdmapResMap::IdmapResMap(const Idmap_data_header* data_header,
const Idmap_target_entry* entries,
const Idmap_target_entry_inline* inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ const ConfigDescription* configs,
uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table)
: data_header_(data_header),
entries_(entries),
inline_entries_(inline_entries),
+ inline_entry_values_(inline_entry_values),
+ configurations_(configs),
target_assigned_package_id_(target_assigned_package_id),
overlay_ref_table_(overlay_ref_table) { }
@@ -183,7 +195,13 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const {
if (inline_entry != end_inline_entry &&
(0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
- return Result(inline_entry->value);
+ std::map<ConfigDescription, Res_value> values_map;
+ for (int i = 0; i < inline_entry->value_count; i++) {
+ const auto& value = inline_entry_values_[inline_entry->start_value_index + i];
+ const auto& config = configurations_[value.config_index];
+ values_map[config] = value.value;
+ }
+ return Result(std::move(values_map));
}
return {};
}
@@ -232,28 +250,29 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size
}
} // namespace
-LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
- const Idmap_header* header,
+LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header,
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
const Idmap_target_entry_inline* target_inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ const ConfigDescription* configs,
const Idmap_overlay_entry* overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
- std::string_view overlay_apk_path,
- std::string_view target_apk_path)
- : header_(header),
- data_header_(data_header),
- target_entries_(target_entries),
- target_inline_entries_(target_inline_entries),
- overlay_entries_(overlay_entries),
- string_pool_(std::move(string_pool)),
- idmap_path_(std::move(idmap_path)),
- overlay_apk_path_(overlay_apk_path),
- target_apk_path_(target_apk_path),
- idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
-
-std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data) {
+ std::string_view overlay_apk_path, std::string_view target_apk_path)
+ : header_(header),
+ data_header_(data_header),
+ target_entries_(target_entries),
+ target_inline_entries_(target_inline_entries),
+ inline_entry_values_(inline_entry_values),
+ configurations_(configs),
+ overlay_entries_(overlay_entries),
+ string_pool_(std::move(string_pool)),
+ idmap_path_(std::move(idmap_path)),
+ overlay_apk_path_(overlay_apk_path),
+ target_apk_path_(target_apk_path),
+ idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
+
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
ATRACE_CALL();
size_t data_size = idmap_data.size();
auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
@@ -303,6 +322,21 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
if (target_inline_entries == nullptr) {
return {};
}
+
+ auto target_inline_entry_values = ReadType<Idmap_target_entry_inline_value>(
+ &data_ptr, &data_size, "target inline values",
+ dtohl(data_header->target_inline_entry_value_count));
+ if (target_inline_entry_values == nullptr) {
+ return {};
+ }
+
+ auto configurations = ReadType<ConfigDescription>(
+ &data_ptr, &data_size, "configurations",
+ dtohl(data_header->configuration_count));
+ if (configurations == nullptr) {
+ return {};
+ }
+
auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline",
dtohl(data_header->overlay_entry_count));
if (overlay_entries == nullptr) {
@@ -328,9 +362,9 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
// Can't use make_unique because LoadedIdmap constructor is private.
return std::unique_ptr<LoadedIdmap>(
- new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
- target_inline_entries, overlay_entries, std::move(idmap_string_pool),
- *target_path, *overlay_path));
+ new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
+ target_inline_entries, target_inline_entry_values, configurations,
+ overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
}
bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 35b6170fae5b..c0fdfe25da21 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -88,7 +88,9 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) {
// Make sure that there is enough room for the entry offsets.
const size_t offsets_offset = dtohs(header->header.headerSize);
const size_t entries_offset = dtohl(header->entriesStart);
- const size_t offsets_length = sizeof(uint32_t) * entry_count;
+ const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16
+ ? sizeof(uint16_t) * entry_count
+ : sizeof(uint32_t) * entry_count;
if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.";
@@ -107,8 +109,8 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) {
return true;
}
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
- incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +138,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(IOError::PAGES_MISSING);
}
- const size_t entry_size = dtohs(entry->size);
+ const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
@@ -149,6 +151,11 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}
+ // If entry is compact, value is already encoded, and a compact entry
+ // cannot be a map_entry, we are done verifying
+ if (entry->is_compact())
+ return entry.verified();
+
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +199,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}
}
- return {};
+ return entry.verified();
}
LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +235,7 @@ uint32_t LoadedPackage::iterator::operator*() const {
entryIndex_);
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
@@ -242,14 +249,13 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset(
// The configuration matches and is better than the previous selection.
// Find the entry value if it exists for this configuration.
const size_t entry_count = dtohl(type_chunk->entryCount);
- const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+ const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize));
// Check if there is the desired entry in this type.
if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
// This is encoded as a sparse map, so perform a binary search.
bool error = false;
- auto sparse_indices = type_chunk.offset(offsets_offset)
- .convert<ResTable_sparseTypeEntry>().iterator();
+ auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator();
auto sparse_indices_end = sparse_indices + entry_count;
auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
[&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry,
@@ -284,26 +290,36 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset(
return base::unexpected(std::nullopt);
}
- const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index;
- if (UNLIKELY(!entry_offset_ptr)) {
- return base::unexpected(IOError::PAGES_MISSING);
+ uint32_t result;
+
+ if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = offset_from16(entry_offset_ptr.value());
+ } else {
+ const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = dtohl(entry_offset_ptr.value());
}
- const uint32_t value = dtohl(entry_offset_ptr.value());
- if (value == ResTable_type::NO_ENTRY) {
+ if (result == ResTable_type::NO_ENTRY) {
return base::unexpected(std::nullopt);
}
-
- return value;
+ return result;
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+ uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
- return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+ return valid;
}
base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -376,31 +392,42 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName(
for (const auto& type_entry : type_spec->type_entries) {
const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
- size_t entry_count = dtohl(type->entryCount);
- for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
- auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() +
- entry_idx;
- if (!entry_offset_ptr) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
+ const size_t entry_count = dtohl(type->entryCount);
+ const auto entry_offsets = type.offset(dtohs(type->header.headerSize));
+ for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
uint32_t offset;
uint16_t res_idx;
if (type->flags & ResTable_type::FLAG_SPARSE) {
- auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+ auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx;
+ if (!sparse_entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
offset = dtohs(sparse_entry->offset) * 4u;
res_idx = dtohs(sparse_entry->idx);
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entry = entry_offsets.convert<uint16_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = offset_from16(entry.value());
+ res_idx = entry_idx;
} else {
- offset = dtohl(entry_offset_ptr.value());
+ auto entry = entry_offsets.convert<uint32_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = dtohl(entry.value());
res_idx = entry_idx;
}
+
if (offset != ResTable_type::NO_ENTRY) {
auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
if (!entry) {
return base::unexpected(IOError::PAGES_MISSING);
}
- if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+ if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
@@ -618,16 +645,16 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
std::string name;
- util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+ util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
std::string actor;
- util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
- if (loaded_package->overlayable_map_.find(name) !=
- loaded_package->overlayable_map_.end()) {
- LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+ util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+ auto [name_to_actor_it, inserted] =
+ loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+ if (!inserted) {
+ LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+ << name_to_actor_it->first << "'.";
return {};
}
- loaded_package->overlayable_map_.emplace(name, actor);
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -642,7 +669,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
return {};
}
-
if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
< dtohl(policy_header->entry_count)) {
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -664,8 +690,8 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
// Add the pairing of overlayable properties and resource ids to the package
OverlayableInfo overlayable_info {
- .name = name,
- .actor = actor,
+ .name = name_to_actor_it->first,
+ .actor = name_to_actor_it->second,
.policy_flags = policy_header->policy_flags
};
loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -709,6 +735,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const auto entry_end = entry_begin + dtohl(lib_alias->count);
std::unordered_set<uint32_t> finalized_ids;
finalized_ids.reserve(entry_end - entry_begin);
+ loaded_package->alias_id_map_.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??";
@@ -722,13 +749,20 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
auto staged_id = dtohl(entry_iter->stagedResId);
- auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
- if (!success) {
+ loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+ }
+
+ std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first < r.first; });
+ const auto duplicate_it =
+ std::adjacent_find(loaded_package->alias_id_map_.begin(),
+ loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first == r.first; });
+ if (duplicate_it != loaded_package->alias_id_map_.end()) {
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
- staged_id);
+ duplicate_it->first);
return {};
}
- }
} break;
default:
@@ -820,6 +854,13 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
return true;
}
+bool LoadedArsc::LoadStringPool(const LoadedIdmap* loaded_idmap) {
+ if (loaded_idmap != nullptr) {
+ global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
+ }
+ return true;
+}
+
std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
const size_t length,
const LoadedIdmap* loaded_idmap,
@@ -855,6 +896,16 @@ std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
return loaded_arsc;
}
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const LoadedIdmap* loaded_idmap) {
+ ATRACE_NAME("LoadedArsc::Load");
+
+ // Not using make_unique because the constructor is private.
+ std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+ loaded_arsc->LoadStringPool(loaded_idmap);
+ return loaded_arsc;
+}
+
+
std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
return std::unique_ptr<LoadedArsc>(new LoadedArsc());
}
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index d87a3ce72177..272a988ec55a 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -66,7 +66,7 @@ static inline bool is_number(const std::string& str) {
return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(StringPiece str) {
// A locale (as specified in the filter) is an underscore separated name such
// as "en_US", "en_Latn_US", or "en_US_POSIX".
std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -132,11 +132,11 @@ bool LocaleValue::InitFromFilterString(const StringPiece& str) {
return true;
}
-bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
return InitFromBcp47TagImpl(bcp47tag, '-');
}
-bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
if (subtags.size() == 1) {
set_language(subtags[0].c_str());
diff --git a/libs/androidfw/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp
index 026912883a73..8ddc57240129 100644
--- a/libs/androidfw/PosixUtils.cpp
+++ b/libs/androidfw/PosixUtils.cpp
@@ -17,7 +17,7 @@
#ifdef _WIN32
// nothing to see here
#else
-#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -29,45 +29,42 @@
#include "androidfw/PosixUtils.h"
-namespace {
-
-std::unique_ptr<std::string> ReadFile(int fd) {
- std::unique_ptr<std::string> str(new std::string());
+static std::optional<std::string> ReadFile(int fd) {
+ std::string str;
char buf[1024];
ssize_t r;
while ((r = read(fd, buf, sizeof(buf))) > 0) {
- str->append(buf, r);
+ str.append(buf, r);
}
if (r != 0) {
- return nullptr;
+ return std::nullopt;
}
- return str;
-}
-
+ return std::move(str);
}
namespace android {
namespace util {
-std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) {
- int stdout[2]; // stdout[0] read, stdout[1] write
+ProcResult ExecuteBinary(const std::vector<std::string>& argv) {
+ int stdout[2]; // [0] read, [1] write
if (pipe(stdout) != 0) {
- PLOG(ERROR) << "pipe";
- return nullptr;
+ PLOG(ERROR) << "out pipe";
+ return ProcResult{-1};
}
- int stderr[2]; // stdout[0] read, stdout[1] write
+ int stderr[2]; // [0] read, [1] write
if (pipe(stderr) != 0) {
- PLOG(ERROR) << "pipe";
+ PLOG(ERROR) << "err pipe";
close(stdout[0]);
close(stdout[1]);
- return nullptr;
+ return ProcResult{-1};
}
auto gid = getgid();
auto uid = getuid();
- char const** argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1));
+ // better keep no C++ objects going into the child here
+ auto argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1));
for (size_t i = 0; i < argv.size(); i++) {
argv0[i] = argv[i].c_str();
}
@@ -76,8 +73,12 @@ std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv)
switch (pid) {
case -1: // error
free(argv0);
+ close(stdout[0]);
+ close(stdout[1]);
+ close(stderr[0]);
+ close(stderr[1]);
PLOG(ERROR) << "fork";
- return nullptr;
+ return ProcResult{-1};
case 0: // child
if (setgid(gid) != 0) {
PLOG(ERROR) << "setgid";
@@ -109,17 +110,16 @@ std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv)
if (!WIFEXITED(status)) {
close(stdout[0]);
close(stderr[0]);
- return nullptr;
+ return ProcResult{-1};
}
- std::unique_ptr<ProcResult> result(new ProcResult());
- result->status = status;
- const auto out = ReadFile(stdout[0]);
- result->stdout_str = out ? *out : "";
+ ProcResult result(status);
+ auto out = ReadFile(stdout[0]);
+ result.stdout_str = out ? std::move(*out) : "";
close(stdout[0]);
- const auto err = ReadFile(stderr[0]);
- result->stderr_str = err ? *err : "";
+ auto err = ReadFile(stderr[0]);
+ result.stderr_str = err ? std::move(*err) : "";
close(stderr[0]);
- return result;
+ return std::move(result);
}
}
diff --git a/libs/androidfw/ResourceTimer.cpp b/libs/androidfw/ResourceTimer.cpp
new file mode 100644
index 000000000000..44128d9e4e3d
--- /dev/null
+++ b/libs/androidfw/ResourceTimer.cpp
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include <map>
+#include <atomic>
+
+#include <utils/Log.h>
+#include <androidfw/ResourceTimer.h>
+
+// The following block allows compilation on windows, which does not have getuid().
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#define getuid() (getUidWindows_)
+#endif
+
+namespace android {
+
+namespace {
+
+#ifdef _WIN32
+// A temporary to confuse lint into thinking that getuid() on windows might return something other
+// than zero.
+int getUidWindows_ = 0;
+#endif
+
+// The number of nanoseconds in a microsecond.
+static const unsigned int US = 1000;
+// The number of nanoseconds in a second.
+static const unsigned int S = 1000 * 1000 * 1000;
+
+// Return the difference between two timespec values. The difference is in nanoseconds. If the
+// return value would exceed 2s (2^31 nanoseconds) then UINT_MAX is returned.
+unsigned int diffInNs(timespec const &a, timespec const &b) {
+ timespec r = { 0, 0 };
+ r.tv_nsec = a.tv_nsec - b.tv_nsec;
+ if (r.tv_nsec < 0) {
+ r.tv_sec = -1;
+ r.tv_nsec += S;
+ }
+ r.tv_sec = r.tv_sec + (a.tv_sec - b.tv_sec);
+ if (r.tv_sec > 2) return UINT_MAX;
+ unsigned int result = (r.tv_sec * S) + r.tv_nsec;
+ if (result > 2 * S) return UINT_MAX;
+ return result;
+}
+
+}
+
+ResourceTimer::ResourceTimer(Counter api)
+ : active_(enabled_.load()),
+ api_(api) {
+ if (active_) {
+ clock_gettime(CLOCK_MONOTONIC, &start_);
+ }
+}
+
+ResourceTimer::~ResourceTimer() {
+ record();
+}
+
+void ResourceTimer::enable() {
+ if (!enabled_.load()) counter_ = new GuardedTimer[ResourceTimer::counterSize];
+ enabled_.store(true);
+}
+
+void ResourceTimer::cancel() {
+ active_ = false;
+}
+
+void ResourceTimer::record() {
+ if (!active_) return;
+
+ struct timespec end;
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ // Get the difference in microseconds.
+ const unsigned int ticks = diffInNs(end, start_);
+ ScopedTimer t(counter_[toIndex(api_)]);
+ t->record(ticks);
+ active_ = false;
+}
+
+bool ResourceTimer::copy(int counter, Timer &dst, bool reset) {
+ ScopedTimer t(counter_[counter]);
+ if (t->count == 0) {
+ dst.reset();
+ if (reset) t->reset();
+ return false;
+ }
+ Timer::copy(dst, *t, reset);
+ return true;
+}
+
+void ResourceTimer::reset() {
+ for (int i = 0; i < counterSize; i++) {
+ ScopedTimer t(counter_[i]);
+ t->reset();
+ }
+}
+
+ResourceTimer::Timer::Timer() {
+ // Ensure newly-created objects are zeroed.
+ memset(buckets, 0, sizeof(buckets));
+ reset();
+}
+
+ResourceTimer::Timer::~Timer() {
+ for (int d = 0; d < MaxDimension; d++) {
+ delete[] buckets[d];
+ }
+}
+
+void ResourceTimer::Timer::freeBuckets() {
+ for (int d = 0; d < MaxDimension; d++) {
+ delete[] buckets[d];
+ buckets[d] = 0;
+ }
+}
+
+void ResourceTimer::Timer::reset() {
+ count = total = mintime = maxtime = 0;
+ memset(largest, 0, sizeof(largest));
+ memset(&pvalues, 0, sizeof(pvalues));
+ // Zero the histogram, keeping any allocated dimensions.
+ for (int d = 0; d < MaxDimension; d++) {
+ if (buckets[d] != 0) memset(buckets[d], 0, sizeof(int) * MaxBuckets);
+ }
+}
+
+void ResourceTimer::Timer::copy(Timer &dst, Timer &src, bool reset) {
+ dst.freeBuckets();
+ dst = src;
+ // Clean up the histograms.
+ if (reset) {
+ // Do NOT free the src buckets because they being used by dst.
+ memset(src.buckets, 0, sizeof(src.buckets));
+ src.reset();
+ } else {
+ for (int d = 0; d < MaxDimension; d++) {
+ if (src.buckets[d] != nullptr) {
+ dst.buckets[d] = new int[MaxBuckets];
+ memcpy(dst.buckets[d], src.buckets[d], sizeof(int) * MaxBuckets);
+ }
+ }
+ }
+}
+
+void ResourceTimer::Timer::record(int ticks) {
+ // Record that the event happened.
+ count++;
+
+ total += ticks;
+ if (mintime == 0 || ticks < mintime) mintime = ticks;
+ if (ticks > maxtime) maxtime = ticks;
+
+ // Do not add oversized events to the histogram.
+ if (ticks != UINT_MAX) {
+ for (int d = 0; d < MaxDimension; d++) {
+ if (ticks < range[d]) {
+ if (buckets[d] == 0) {
+ buckets[d] = new int[MaxBuckets];
+ memset(buckets[d], 0, sizeof(int) * MaxBuckets);
+ }
+ if (ticks < width[d]) {
+ // Special case: never write to bucket 0 because it complicates the percentile logic.
+ // However, this is always the smallest possible value to it is very unlikely to ever
+ // affect any of the percentile results.
+ buckets[d][1]++;
+ } else {
+ buckets[d][ticks / width[d]]++;
+ }
+ break;
+ }
+ }
+ }
+
+ // The list of largest times is sorted with the biggest value at index 0 and the smallest at
+ // index MaxLargest-1. The incoming tick count should be added to the array only if it is
+ // larger than the current value at MaxLargest-1.
+ if (ticks > largest[Timer::MaxLargest-1]) {
+ for (size_t i = 0; i < Timer::MaxLargest; i++) {
+ if (ticks > largest[i]) {
+ if (i < Timer::MaxLargest-1) {
+ for (size_t j = Timer::MaxLargest - 1; j > i; j--) {
+ largest[j] = largest[j-1];
+ }
+ }
+ largest[i] = ticks;
+ break;
+ }
+ }
+ }
+}
+
+void ResourceTimer::Timer::Percentile::compute(
+ int cumulative, int current, int count, int width, int time) {
+ nominal = time;
+ nominal_actual = (cumulative * 100) / count;
+ floor = nominal - width;
+ floor_actual = ((cumulative - current) * 100) / count;
+}
+
+void ResourceTimer::Timer::compute() {
+ memset(&pvalues, 0, sizeof(pvalues));
+
+ float l50 = count / 2.0;
+ float l90 = (count * 9.0) / 10.0;
+ float l95 = (count * 95.0) / 100.0;
+ float l99 = (count * 99.0) / 100.0;
+
+ int sum = 0;
+ for (int d = 0; d < MaxDimension; d++) {
+ if (buckets[d] == 0) continue;
+ for (int j = 0; j < MaxBuckets && sum < count; j++) {
+ // Empty buckets don't contribute to the answers. Skip them.
+ if (buckets[d][j] == 0) continue;
+ sum += buckets[d][j];
+ // A word on indexing. j is never zero in the following lines. buckets[0][0] corresponds
+ // to a delay of 0us, which cannot happen. buckets[n][0], for n > 0 overlaps a value in
+ // buckets[n-1], and the code would have stopped there.
+ if (sum >= l50 && pvalues.p50.nominal == 0) {
+ pvalues.p50.compute(sum, buckets[d][j], count, width[d], j * width[d]);
+ }
+ if (sum >= l90 && pvalues.p90.nominal == 0) {
+ pvalues.p90.compute(sum, buckets[d][j], count, width[d], j * width[d]);
+ }
+ if (sum >= l95 && pvalues.p95.nominal == 0) {
+ pvalues.p95.compute(sum, buckets[d][j], count, width[d], j * width[d]);
+ }
+ if (sum >= l99 && pvalues.p99.nominal == 0) {
+ pvalues.p99.compute(sum, buckets[d][j], count, width[d], j * width[d]);
+ }
+ }
+ }
+}
+
+char const *ResourceTimer::toString(ResourceTimer::Counter counter) {
+ switch (counter) {
+ case Counter::GetResourceValue:
+ return "GetResourceValue";
+ case Counter::RetrieveAttributes:
+ return "RetrieveAttributes";
+ };
+ return "Unknown";
+}
+
+std::atomic<bool> ResourceTimer::enabled_(false);
+std::atomic<ResourceTimer::GuardedTimer *> ResourceTimer::counter_(nullptr);
+
+const int ResourceTimer::Timer::range[] = { 100 * US, 1000 * US, 10*1000 * US, 100*1000 * US };
+const int ResourceTimer::Timer::width[] = { 1 * US, 10 * US, 100 * US, 1000 * US };
+
+
+} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623d4205..29d33da6b2f7 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
#include <type_traits>
#include <vector>
+#include <android-base/file.h>
#include <android-base/macros.h>
+#include <android-base/utf8.h>
#include <androidfw/ByteBucketArray.h>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs
}
bool IsFabricatedOverlay(const std::string& path) {
- std::ifstream fin(path);
+ return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+ auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+ if (fd < 0) {
+ return false;
+ }
+ return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
uint32_t magic;
- if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
- return magic == kFabricatedOverlayMagic;
+ if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+ return false;
}
- return false;
+ return magic == kFabricatedOverlayMagic;
}
static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -2092,6 +2105,9 @@ int ResTable_config::compare(const ResTable_config& o) const {
return 1;
}
+ if (grammaticalInflection != o.grammaticalInflection) {
+ return grammaticalInflection < o.grammaticalInflection ? -1 : 1;
+ }
if (screenType != o.screenType) {
return (screenType > o.screenType) ? 1 : -1;
}
@@ -2140,7 +2156,9 @@ int ResTable_config::compareLogical(const ResTable_config& o) const {
if (diff > 0) {
return 1;
}
-
+ if (grammaticalInflection != o.grammaticalInflection) {
+ return grammaticalInflection < o.grammaticalInflection ? -1 : 1;
+ }
if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
}
@@ -2210,6 +2228,7 @@ int ResTable_config::diff(const ResTable_config& o) const {
if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
+ if (grammaticalInflection != o.grammaticalInflection) diffs |= CONFIG_GRAMMATICAL_GENDER;
const int diff = compareLocales(*this, o);
if (diff) diffs |= CONFIG_LOCALE;
@@ -2276,6 +2295,13 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
}
}
+ if (grammaticalInflection || o.grammaticalInflection) {
+ if (grammaticalInflection != o.grammaticalInflection) {
+ if (!grammaticalInflection) return false;
+ if (!o.grammaticalInflection) return true;
+ }
+ }
+
if (screenLayout || o.screenLayout) {
if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0) {
if (!(screenLayout & MASK_LAYOUTDIR)) return false;
@@ -2542,6 +2568,13 @@ bool ResTable_config::isBetterThan(const ResTable_config& o,
return true;
}
+ if (grammaticalInflection || o.grammaticalInflection) {
+ if (grammaticalInflection != o.grammaticalInflection
+ && requested->grammaticalInflection) {
+ return !!grammaticalInflection;
+ }
+ }
+
if (screenLayout || o.screenLayout) {
if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
&& (requested->screenLayout & MASK_LAYOUTDIR)) {
@@ -2841,6 +2874,10 @@ bool ResTable_config::match(const ResTable_config& settings) const {
}
}
+ if (grammaticalInflection && grammaticalInflection != settings.grammaticalInflection) {
+ return false;
+ }
+
if (screenConfig != 0) {
const int layoutDir = screenLayout&MASK_LAYOUTDIR;
const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
@@ -3254,6 +3291,15 @@ String8 ResTable_config::toString() const {
appendDirLocale(res);
+ if ((grammaticalInflection & GRAMMATICAL_INFLECTION_GENDER_MASK) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (grammaticalInflection & GRAMMATICAL_INFLECTION_GENDER_MASK) {
+ case GRAMMATICAL_GENDER_NEUTER: res.append("neuter"); break;
+ case GRAMMATICAL_GENDER_FEMININE: res.append("feminine"); break;
+ case GRAMMATICAL_GENDER_MASCULINE: res.append("masculine"); break;
+ }
+ }
+
if ((screenLayout&MASK_LAYOUTDIR) != 0) {
if (res.size() > 0) res.append("-");
switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
@@ -4487,20 +4533,14 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
return err;
}
- if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
- outValue->size = dtohs(value->size);
- outValue->res0 = value->res0;
- outValue->dataType = value->dataType;
- outValue->data = dtohl(value->data);
+ *outValue = entry.entry->value();
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
@@ -4691,11 +4731,10 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
return err;
}
- const uint16_t entrySize = dtohs(entry.entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+ const uint16_t entrySize = entry.entry->size();
+ const ResTable_map_entry* map_entry = entry.entry->map_entry();
+ const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+ const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
size_t N = count;
@@ -4759,7 +4798,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
- + dtohs(entry.entry->size);
+ + entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
@@ -5137,7 +5176,7 @@ uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const
continue;
}
- if (dtohl(entry->key.index) == (size_t) *ei) {
+ if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
@@ -6600,8 +6639,12 @@ status_t ResTable::getEntry(
// Entry does not exist.
continue;
}
-
- thisOffset = dtohl(eindex[realEntryIndex]);
+ if (thisType->flags & ResTable_type::FLAG_OFFSET16) {
+ auto eindex16 = reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[realEntryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[realEntryIndex]);
+ }
}
if (thisOffset == ResTable_type::NO_ENTRY) {
@@ -6651,8 +6694,8 @@ status_t ResTable::getEntry(
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ if (entry->size() < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}
@@ -6663,7 +6706,7 @@ status_t ResTable::getEntry(
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
- outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
@@ -6880,7 +6923,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
const uint32_t typeSize = dtohl(type->header.size);
const size_t newEntryCount = dtohl(type->entryCount);
-
+ const size_t entrySize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
if (kDebugLoadTableNoisy) {
printf("Type off %p: type=0x%x, headerSize=0x%x, size=%u\n",
(void*)(base-(const uint8_t*)chunk),
@@ -6888,9 +6932,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
dtohs(type->header.headerSize),
typeSize);
}
- if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSize) {
+ if (dtohs(type->header.headerSize)+(entrySize*newEntryCount) > typeSize) {
ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
- (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+ (void*)(dtohs(type->header.headerSize) + (entrySize*newEntryCount)),
typeSize);
return (mError=BAD_TYPE);
}
@@ -6991,11 +7035,10 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
- : mAssignedPackageId(packageId)
+ : mLookupTable()
+ , mAssignedPackageId(packageId)
, mAppAsLib(appAsLib)
{
- memset(mLookupTable, 0, sizeof(mLookupTable));
-
// Reserved package ids
mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7076,10 +7119,6 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI
mLookupTable[buildPackageId] = runtimePackageId;
}
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
- mAliasId[stagedId] = finalizedId;
-}
-
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7089,11 +7128,12 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
return NO_ERROR;
}
- auto alias_id = mAliasId.find(res);
- if (alias_id != mAliasId.end()) {
+ const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+ [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+ if (alias_it != mAliasId.end() && alias_it->first == res) {
// Rewrite the resource id to its alias resource id. Since the alias resource id is a
// compile-time id, it still needs to be resolved further.
- res = alias_id->second;
+ res = alias_it->second;
}
if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
@@ -7653,6 +7693,9 @@ void ResTable::print(bool inclValues) const
if (type->flags & ResTable_type::FLAG_SPARSE) {
printf(" [sparse]");
}
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ printf(" [offset16]");
+ }
}
printf(":\n");
@@ -7684,7 +7727,13 @@ void ResTable::print(bool inclValues) const
thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
} else {
entryId = entryIndex;
- thisOffset = dtohl(eindex[entryIndex]);
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto eindex16 =
+ reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[entryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[entryIndex]);
+ }
if (thisOffset == ResTable_type::NO_ENTRY) {
continue;
}
@@ -7734,7 +7783,7 @@ void ResTable::print(bool inclValues) const
continue;
}
- uintptr_t esize = dtohs(ent->size);
+ uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
@@ -7746,30 +7795,27 @@ void ResTable::print(bool inclValues) const
}
const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
+ const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (bagPtr) {
printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
} else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
+ value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");
if (inclValues) {
- if (valuePtr != NULL) {
+ if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
- } else if (bagPtr != NULL) {
+ } else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 87fb2c038c9f..ccb61561578f 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -18,7 +18,7 @@
namespace android {
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry) {
*out_package = "";
*out_type = "";
@@ -33,16 +33,16 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin
while (current != end) {
if (out_type->size() == 0 && *current == '/') {
has_type_separator = true;
- out_type->assign(start, current - start);
+ *out_type = StringPiece(start, current - start);
start = current + 1;
} else if (out_package->size() == 0 && *current == ':') {
has_package_separator = true;
- out_package->assign(start, current - start);
+ *out_package = StringPiece(start, current - start);
start = current + 1;
}
current++;
}
- out_entry->assign(start, end - start);
+ *out_entry = StringPiece(start, end - start);
return !(has_package_separator && out_package->empty()) &&
!(has_type_separator && out_type->empty());
@@ -50,7 +50,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin
base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
- const StringPiece& package_name) {
+ StringPiece package_name) {
AssetManager2::ResourceName name{
.package = package_name.data(),
.package_len = package_name.size(),
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
new file mode 100644
index 000000000000..1cb8df311c89
--- /dev/null
+++ b/libs/androidfw/StringPool.cpp
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <androidfw/BigBuffer.h>
+#include <androidfw/StringPool.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "android-base/logging.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+
+using ::android::StringPiece;
+
+namespace android {
+
+StringPool::Ref::Ref() : entry_(nullptr) {
+}
+
+StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
+}
+
+StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
+}
+
+StringPool::Ref::~Ref() {
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+}
+
+StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
+ if (rhs.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
+
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
+}
+
+bool StringPool::Ref::operator==(const Ref& rhs) const {
+ return entry_->value == rhs.entry_->value;
+}
+
+bool StringPool::Ref::operator!=(const Ref& rhs) const {
+ return entry_->value != rhs.entry_->value;
+}
+
+const std::string* StringPool::Ref::operator->() const {
+ return &entry_->value;
+}
+
+const std::string& StringPool::Ref::operator*() const {
+ return entry_->value;
+}
+
+size_t StringPool::Ref::index() const {
+ // Account for the styles, which *always* come first.
+ return entry_->pool_->styles_.size() + entry_->index_;
+}
+
+const StringPool::Context& StringPool::Ref::GetContext() const {
+ return entry_->context;
+}
+
+StringPool::StyleRef::StyleRef() : entry_(nullptr) {
+}
+
+StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : entry_(rhs.entry_) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
+}
+
+StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) {
+ if (entry_ != nullptr) {
+ entry_->ref_++;
+ }
+}
+
+StringPool::StyleRef::~StyleRef() {
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+}
+
+StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
+ if (rhs.entry_ != nullptr) {
+ rhs.entry_->ref_++;
+ }
+
+ if (entry_ != nullptr) {
+ entry_->ref_--;
+ }
+ entry_ = rhs.entry_;
+ return *this;
+}
+
+bool StringPool::StyleRef::operator==(const StyleRef& rhs) const {
+ if (entry_->value != rhs.entry_->value) {
+ return false;
+ }
+
+ if (entry_->spans.size() != rhs.entry_->spans.size()) {
+ return false;
+ }
+
+ auto rhs_iter = rhs.entry_->spans.begin();
+ for (const Span& span : entry_->spans) {
+ const Span& rhs_span = *rhs_iter;
+ if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char ||
+ span.name != rhs_span.name) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const {
+ return !operator==(rhs);
+}
+
+const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
+ return entry_;
+}
+
+const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
+ return *entry_;
+}
+
+size_t StringPool::StyleRef::index() const {
+ return entry_->index_;
+}
+
+const StringPool::Context& StringPool::StyleRef::GetContext() const {
+ return entry_->context;
+}
+
+StringPool::Ref StringPool::MakeRef(StringPiece str) {
+ return MakeRefImpl(str, Context{}, true);
+}
+
+StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) {
+ return MakeRefImpl(str, context, true);
+}
+
+StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) {
+ if (unique) {
+ auto range = indexed_strings_.equal_range(str);
+ for (auto iter = range.first; iter != range.second; ++iter) {
+ if (context.priority == iter->second->context.priority) {
+ return Ref(iter->second);
+ }
+ }
+ }
+
+ std::unique_ptr<Entry> entry(new Entry());
+ entry->value = std::string(str);
+ entry->context = context;
+ entry->index_ = strings_.size();
+ entry->ref_ = 0;
+ entry->pool_ = this;
+
+ Entry* borrow = entry.get();
+ strings_.emplace_back(std::move(entry));
+ indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
+ return Ref(borrow);
+}
+
+StringPool::Ref StringPool::MakeRef(const Ref& ref) {
+ if (ref.entry_->pool_ == this) {
+ return ref;
+ }
+ return MakeRef(ref.entry_->value, ref.entry_->context);
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
+ return MakeRef(str, Context{});
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) {
+ std::unique_ptr<StyleEntry> entry(new StyleEntry());
+ entry->value = str.str;
+ entry->context = context;
+ entry->index_ = styles_.size();
+ entry->ref_ = 0;
+ for (const android::Span& span : str.spans) {
+ entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char});
+ }
+
+ StyleEntry* borrow = entry.get();
+ styles_.emplace_back(std::move(entry));
+ return StyleRef(borrow);
+}
+
+StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) {
+ std::unique_ptr<StyleEntry> entry(new StyleEntry());
+ entry->value = ref.entry_->value;
+ entry->context = ref.entry_->context;
+ entry->index_ = styles_.size();
+ entry->ref_ = 0;
+ for (const Span& span : ref.entry_->spans) {
+ entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char});
+ }
+
+ StyleEntry* borrow = entry.get();
+ styles_.emplace_back(std::move(entry));
+ return StyleRef(borrow);
+}
+
+void StringPool::ReAssignIndices() {
+ // Assign the style indices.
+ const size_t style_len = styles_.size();
+ for (size_t index = 0; index < style_len; index++) {
+ styles_[index]->index_ = index;
+ }
+
+ // Assign the string indices.
+ const size_t string_len = strings_.size();
+ for (size_t index = 0; index < string_len; index++) {
+ strings_[index]->index_ = index;
+ }
+}
+
+void StringPool::Merge(StringPool&& pool) {
+ // First, change the owning pool for the incoming strings.
+ for (std::unique_ptr<Entry>& entry : pool.strings_) {
+ entry->pool_ = this;
+ }
+
+ // Now move the styles, strings, and indices over.
+ std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_));
+ pool.styles_.clear();
+ std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_));
+ pool.strings_.clear();
+ indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end());
+ pool.indexed_strings_.clear();
+
+ ReAssignIndices();
+}
+
+void StringPool::HintWillAdd(size_t string_count, size_t style_count) {
+ strings_.reserve(strings_.size() + string_count);
+ styles_.reserve(styles_.size() + style_count);
+}
+
+void StringPool::Prune() {
+ const auto iter_end = indexed_strings_.end();
+ auto index_iter = indexed_strings_.begin();
+ while (index_iter != iter_end) {
+ if (index_iter->second->ref_ <= 0) {
+ index_iter = indexed_strings_.erase(index_iter);
+ } else {
+ ++index_iter;
+ }
+ }
+
+ auto end_iter2 =
+ std::remove_if(strings_.begin(), strings_.end(),
+ [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; });
+ auto end_iter3 = std::remove_if(
+ styles_.begin(), styles_.end(),
+ [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; });
+
+ // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry.
+ strings_.erase(end_iter2, strings_.end());
+ styles_.erase(end_iter3, styles_.end());
+
+ ReAssignIndices();
+}
+
+template <typename E>
+static void SortEntries(
+ std::vector<std::unique_ptr<E>>& entries,
+ const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
+ using UEntry = std::unique_ptr<E>;
+
+ if (cmp != nullptr) {
+ std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
+ int r = cmp(a->context, b->context);
+ if (r == 0) {
+ r = a->value.compare(b->value);
+ }
+ return r < 0;
+ });
+ } else {
+ std::sort(entries.begin(), entries.end(),
+ [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
+ }
+}
+
+void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
+ SortEntries(styles_, cmp);
+ SortEntries(strings_, cmp);
+ ReAssignIndices();
+}
+
+template <typename T>
+static T* EncodeLength(T* data, size_t length) {
+ static_assert(std::is_integral<T>::value, "wat.");
+
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ if (length > kMaxSize) {
+ *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8)));
+ }
+ *data++ = length;
+ return data;
+}
+
+/**
+ * Returns the maximum possible string length that can be successfully encoded
+ * using 2 units of the specified T.
+ * EncodeLengthMax<char> -> maximum unit length of 0x7FFF
+ * EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF
+ **/
+template <typename T>
+static size_t EncodeLengthMax() {
+ static_assert(std::is_integral<T>::value, "wat.");
+
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1);
+ constexpr size_t max = kMask - 1;
+ return max;
+}
+
+/**
+ * Returns the number of units (1 or 2) needed to encode the string length
+ * before writing the string.
+ */
+template <typename T>
+static size_t EncodedLengthUnits(size_t length) {
+ static_assert(std::is_integral<T>::value, "wat.");
+
+ constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1);
+ constexpr size_t kMaxSize = kMask - 1;
+ return length > kMaxSize ? 2 : 1;
+}
+
+const std::string kStringTooLarge = "STRING_TOO_LARGE";
+
+static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out,
+ IDiagnostics* diag) {
+ if (utf8) {
+ const std::string& encoded = util::Utf8ToModifiedUtf8(str);
+ const ssize_t utf16_length =
+ utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size());
+ CHECK(utf16_length >= 0);
+
+ // Make sure the lengths to be encoded do not exceed the maximum length that
+ // can be encoded using chars
+ if ((((size_t)encoded.size()) > EncodeLengthMax<char>()) ||
+ (((size_t)utf16_length) > EncodeLengthMax<char>())) {
+ diag->Error(DiagMessage() << "string too large to encode using UTF-8 "
+ << "written instead as '" << kStringTooLarge << "'");
+
+ EncodeString(kStringTooLarge, utf8, out, diag);
+ return false;
+ }
+
+ const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
+ EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1;
+
+ char* data = out->NextBlock<char>(total_size);
+
+ // First encode the UTF16 string length.
+ data = EncodeLength(data, utf16_length);
+
+ // Now encode the size of the real UTF8 string.
+ data = EncodeLength(data, encoded.size());
+ strncpy(data, encoded.data(), encoded.size());
+
+ } else {
+ const std::u16string encoded = util::Utf8ToUtf16(str);
+ const ssize_t utf16_length = encoded.size();
+
+ // Make sure the length to be encoded does not exceed the maximum possible
+ // length that can be encoded
+ if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) {
+ diag->Error(DiagMessage() << "string too large to encode using UTF-16 "
+ << "written instead as '" << kStringTooLarge << "'");
+
+ EncodeString(kStringTooLarge, utf8, out, diag);
+ return false;
+ }
+
+ // Total number of 16-bit words to write.
+ const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
+
+ char16_t* data = out->NextBlock<char16_t>(total_size);
+
+ // Encode the actual UTF16 string length.
+ data = EncodeLength(data, utf16_length);
+ const size_t byte_length = encoded.size() * sizeof(char16_t);
+
+ // NOTE: For some reason, strncpy16(data, entry->value.data(),
+ // entry->value.size()) truncates the string.
+ memcpy(data, encoded.data(), byte_length);
+
+ // The null-terminating character is already here due to the block of data
+ // being set to 0s on allocation.
+ }
+
+ return true;
+}
+
+bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag) {
+ bool no_error = true;
+ const size_t start_index = out->size();
+ android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>();
+ header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE);
+ header->header.headerSize = util::HostToDevice16(sizeof(*header));
+ header->stringCount = util::HostToDevice32(pool.size());
+ header->styleCount = util::HostToDevice32(pool.styles_.size());
+ if (utf8) {
+ header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ }
+
+ uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
+ uint32_t* style_indices =
+ pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr;
+
+ const size_t before_strings_index = out->size();
+ header->stringsStart = before_strings_index - start_index;
+
+ // Styles always come first.
+ for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
+ *indices++ = out->size() - before_strings_index;
+ no_error = EncodeString(entry->value, utf8, out, diag) && no_error;
+ }
+
+ for (const std::unique_ptr<Entry>& entry : pool.strings_) {
+ *indices++ = out->size() - before_strings_index;
+ no_error = EncodeString(entry->value, utf8, out, diag) && no_error;
+ }
+
+ out->Align4();
+
+ if (style_indices != nullptr) {
+ const size_t before_styles_index = out->size();
+ header->stylesStart = util::HostToDevice32(before_styles_index - start_index);
+
+ for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
+ *style_indices++ = out->size() - before_styles_index;
+
+ if (!entry->spans.empty()) {
+ android::ResStringPool_span* span =
+ out->NextBlock<android::ResStringPool_span>(entry->spans.size());
+ for (const Span& s : entry->spans) {
+ span->name.index = util::HostToDevice32(s.name.index());
+ span->firstChar = util::HostToDevice32(s.first_char);
+ span->lastChar = util::HostToDevice32(s.last_char);
+ span++;
+ }
+ }
+
+ uint32_t* spanEnd = out->NextBlock<uint32_t>();
+ *spanEnd = android::ResStringPool_span::END;
+ }
+
+ // The error checking code in the platform looks for an entire
+ // ResStringPool_span structure worth of 0xFFFFFFFF at the end
+ // of the style block, so fill in the remaining 2 32bit words
+ // with 0xFFFFFFFF.
+ const size_t padding_length =
+ sizeof(android::ResStringPool_span) - sizeof(android::ResStringPool_span::name);
+ uint8_t* padding = out->NextBlock<uint8_t>(padding_length);
+ memset(padding, 0xff, padding_length);
+ out->Align4();
+ }
+ header->header.size = util::HostToDevice32(out->size() - start_index);
+ return no_error;
+}
+
+bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) {
+ return Flatten(out, pool, true, diag);
+}
+
+bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) {
+ return Flatten(out, pool, false, diag);
+}
+
+} // namespace android
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa197a94d..70d14a11830e 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -59,7 +59,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
- if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+ const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
+ if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) {
ALOGE("Type's entry indices extend beyond its boundaries");
return NULL;
}
@@ -73,6 +75,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
}
entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices);
+ entryOffset = offset_from16(entryIndices16[mIndex]);
} else {
entryOffset = dtohl(entryIndices[mIndex]);
}
@@ -91,11 +96,11 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
- } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
- } else if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ } else if (entry->size() < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 59c9d640bb91..be55fe8b4bb6 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -42,7 +42,7 @@ void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out
}
}
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
ssize_t utf16_length =
utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
if (utf16_length <= 0) {
@@ -56,7 +56,7 @@ std::u16string Utf8ToUtf16(const StringPiece& utf8) {
return utf16;
}
-std::string Utf16ToUtf8(const StringPiece16& utf16) {
+std::string Utf16ToUtf8(StringPiece16 utf16) {
ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
if (utf8_length <= 0) {
return {};
@@ -68,28 +68,151 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) {
return utf8;
}
-static std::vector<std::string> SplitAndTransform(
- const StringPiece& str, char sep, const std::function<char(char)>& f) {
+std::string Utf8ToModifiedUtf8(std::string_view utf8) {
+ // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
+ // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
+ // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
+ // codepoints replaced with 2 3 byte surrogate pairs
+ size_t modified_size = 0;
+ const size_t size = utf8.size();
+ for (size_t i = 0; i < size; i++) {
+ if (((uint8_t)utf8[i] >> 4) == 0xF) {
+ modified_size += 6;
+ i += 3;
+ } else {
+ modified_size++;
+ }
+ }
+
+ // Early out if no 4 byte codepoints are found
+ if (size == modified_size) {
+ return std::string(utf8);
+ }
+
+ std::string output;
+ output.reserve(modified_size);
+ for (size_t i = 0; i < size; i++) {
+ if (((uint8_t)utf8[i] >> 4) == 0xF) {
+ int32_t codepoint = utf32_from_utf8_at(utf8.data(), size, i, nullptr);
+
+ // Calculate the high and low surrogates as UTF-16 would
+ int32_t high = ((codepoint - 0x10000) / 0x400) + 0xD800;
+ int32_t low = ((codepoint - 0x10000) % 0x400) + 0xDC00;
+
+ // Encode each surrogate in UTF-8
+ output.push_back((char)(0xE4 | ((high >> 12) & 0xF)));
+ output.push_back((char)(0x80 | ((high >> 6) & 0x3F)));
+ output.push_back((char)(0x80 | (high & 0x3F)));
+ output.push_back((char)(0xE4 | ((low >> 12) & 0xF)));
+ output.push_back((char)(0x80 | ((low >> 6) & 0x3F)));
+ output.push_back((char)(0x80 | (low & 0x3F)));
+ i += 3;
+ } else {
+ output.push_back(utf8[i]);
+ }
+ }
+
+ return output;
+}
+
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) {
+ // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8
+ // representation.
+ std::string output;
+ output.reserve(modified_utf8.size());
+
+ size_t index = 0;
+ const size_t modified_size = modified_utf8.size();
+ while (index < modified_size) {
+ size_t next_index;
+ int32_t high_surrogate =
+ utf32_from_utf8_at(modified_utf8.data(), modified_size, index, &next_index);
+ if (high_surrogate < 0) {
+ return {};
+ }
+
+ // Check that the first codepoint is within the high surrogate range
+ if (high_surrogate >= 0xD800 && high_surrogate <= 0xDB7F) {
+ int32_t low_surrogate =
+ utf32_from_utf8_at(modified_utf8.data(), modified_size, next_index, &next_index);
+ if (low_surrogate < 0) {
+ return {};
+ }
+
+ // Check that the second codepoint is within the low surrogate range
+ if (low_surrogate >= 0xDC00 && low_surrogate <= 0xDFFF) {
+ const char32_t codepoint =
+ (char32_t)(((high_surrogate - 0xD800) * 0x400) + (low_surrogate - 0xDC00) + 0x10000);
+
+ // The decoded codepoint should represent a 4 byte, UTF-8 character
+ const size_t utf8_length = (size_t)utf32_to_utf8_length(&codepoint, 1);
+ if (utf8_length != 4) {
+ return {};
+ }
+
+ // Encode the UTF-8 representation of the codepoint into the string
+ const size_t start_index = output.size();
+ output.resize(start_index + utf8_length);
+ char* start = &output[start_index];
+ utf32_to_utf8((char32_t*)&codepoint, 1, start, utf8_length + 1);
+
+ index = next_index;
+ continue;
+ }
+ }
+
+ // Append non-surrogate pairs to the output string
+ for (size_t i = index; i < next_index; i++) {
+ output.push_back(modified_utf8[i]);
+ }
+ index = next_index;
+ }
+ return output;
+}
+
+template <class Func>
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) {
std::vector<std::string> parts;
const StringPiece::const_iterator end = std::end(str);
StringPiece::const_iterator start = std::begin(str);
StringPiece::const_iterator current;
do {
current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).to_string());
- if (f) {
- std::string& part = parts.back();
- std::transform(part.begin(), part.end(), part.begin(), f);
- }
+ parts.emplace_back(StringPiece(start, current - start));
+ std::string& part = parts.back();
+ std::transform(part.begin(), part.end(), part.begin(), f);
start = current + 1;
} while (current != end);
return parts;
}
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
- return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+ return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); });
}
+std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
+ auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+ uint8_t* p = data.get();
+ for (const auto& block : buffer) {
+ memcpy(p, block.buffer.get(), block.size);
+ p += block.size;
+ }
+ return data;
+}
+
+StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) {
+ if (auto str = pool.stringAt(idx); str.ok()) {
+ return *str;
+ }
+ return StringPiece16();
+}
+
+std::string GetString(const android::ResStringPool& pool, size_t idx) {
+ if (auto str = pool.string8At(idx); str.ok()) {
+ return ModifiedUtf8ToUtf8(*str);
+ }
+ return Utf16ToUtf8(GetString16(pool, idx));
+}
} // namespace util
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ApkParsing.h b/libs/androidfw/include/androidfw/ApkParsing.h
new file mode 100644
index 000000000000..194eaae8e12a
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ApkParsing.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string_view>
+#include <sys/types.h>
+
+extern const std::string_view APK_LIB;
+extern const size_t APK_LIB_LEN;
+
+namespace android::util {
+// Checks if filename is a valid library path and returns a pointer to the last slash in the path
+// if it is, nullptr otherwise
+const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit, bool debuggable);
+
+// Equivalent to android.os.FileUtils.isFilenameSafe
+bool isFilenameSafe(const char* filename);
+}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1bde792da2ba..f10cb9bf480a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
#ifndef ANDROIDFW_ASSETMANAGER2_H_
#define ANDROIDFW_ASSETMANAGER2_H_
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include <array>
@@ -104,7 +105,7 @@ class AssetManager2 {
// new resource IDs.
bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
- inline const std::vector<const ApkAssets*> GetApkAssets() const {
+ inline const std::vector<const ApkAssets*>& GetApkAssets() const {
return apk_assets_;
}
@@ -124,8 +125,7 @@ class AssetManager2 {
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
// Returns a string representation of the overlayable API of a package.
- bool GetOverlayablesToString(const android::StringPiece& package_name,
- std::string* out) const;
+ bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
uint32_t package_id) const;
@@ -321,17 +321,8 @@ class AssetManager2 {
// Creates a new Theme from this AssetManager.
std::unique_ptr<Theme> NewTheme();
- void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
- package_property_t excluded_property_flags = 0U) const {
- for (const PackageGroup& package_group : package_groups_) {
- const auto loaded_package = package_group.packages_.front().loaded_package_;
- if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
- && !func(loaded_package->GetPackageName(),
- package_group.dynamic_ref_table->mAssignedPackageId)) {
- return;
- }
- }
- }
+ void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags = 0U) const;
void DumpToLog() const;
@@ -572,6 +563,7 @@ class Theme {
AssetManager2* asset_manager_ = nullptr;
uint32_t type_spec_flags_ = 0u;
+ std::vector<uint32_t> keys_;
std::vector<Entry> entries_;
};
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 966ec74c1786..d33c325ff369 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -20,6 +20,7 @@
#include <memory>
#include <string>
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include "android-base/unique_fd.h"
@@ -46,7 +47,7 @@ struct AssetsProvider {
// Iterate over all files and directories provided by the interface. The order of iteration is
// stable.
virtual bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+ base::function_ref<void(StringPiece, FileType)> f) const = 0;
// Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an
// APk, a directory, or some other file type.
@@ -80,8 +81,8 @@ struct AssetsProvider {
// Supplies assets from a zip archive.
struct ZipAssetsProvider : public AssetsProvider {
- static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
- package_property_t flags);
+ static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+ base::unique_fd fd = {});
static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
std::string friendly_name,
@@ -90,7 +91,7 @@ struct ZipAssetsProvider : public AssetsProvider {
off64_t len = kUnknownLength);
bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -108,7 +109,12 @@ struct ZipAssetsProvider : public AssetsProvider {
time_t last_mod_time);
struct PathOrDebugName {
- PathOrDebugName(std::string&& value, bool is_path);
+ static PathOrDebugName Path(std::string value) {
+ return {std::move(value), true};
+ }
+ static PathOrDebugName DebugName(std::string value) {
+ return {std::move(value), false};
+ }
// Retrieves the path or null if this class represents a debug name.
WARN_UNUSED const std::string* GetPath() const;
@@ -117,11 +123,16 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED const std::string& GetDebugName() const;
private:
+ PathOrDebugName(std::string value, bool is_path) : value_(std::move(value)), is_path_(is_path) {
+ }
std::string value_;
bool is_path_;
};
- std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_;
+ struct ZipCloser {
+ void operator()(ZipArchive* a) const;
+ };
+ std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
PathOrDebugName name_;
package_property_t flags_;
time_t last_mod_time_;
@@ -132,7 +143,7 @@ struct DirectoryAssetsProvider : public AssetsProvider {
static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -157,7 +168,7 @@ struct MultiAssetsProvider : public AssetsProvider {
std::unique_ptr<AssetsProvider>&& secondary);
bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -181,10 +192,10 @@ struct MultiAssetsProvider : public AssetsProvider {
// Does not provide any assets.
struct EmptyAssetsProvider : public AssetsProvider {
static std::unique_ptr<AssetsProvider> Create();
- static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+ static std::unique_ptr<AssetsProvider> Create(std::string path);
bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
diff --git a/libs/androidfw/include/androidfw/BigBuffer.h b/libs/androidfw/include/androidfw/BigBuffer.h
new file mode 100644
index 000000000000..b99a4edf9d88
--- /dev/null
+++ b/libs/androidfw/include/androidfw/BigBuffer.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_BIG_BUFFER_H
+#define _ANDROID_BIG_BUFFER_H
+
+#include <cstring>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+namespace android {
+
+/**
+ * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory
+ * in which to write without knowing the full size of the entire payload.
+ * This is essentially a list of memory blocks. As one fills up, another
+ * block is allocated and appended to the end of the list.
+ */
+class BigBuffer {
+ public:
+ /**
+ * A contiguous block of allocated memory.
+ */
+ struct Block {
+ /**
+ * Pointer to the memory.
+ */
+ std::unique_ptr<uint8_t[]> buffer;
+
+ /**
+ * Size of memory that is currently occupied. The actual
+ * allocation may be larger.
+ */
+ size_t size;
+
+ private:
+ friend class BigBuffer;
+
+ /**
+ * The size of the memory block allocation.
+ */
+ size_t block_size_;
+ };
+
+ typedef std::vector<Block>::const_iterator const_iterator;
+
+ /**
+ * Create a BigBuffer with block allocation sizes
+ * of block_size.
+ */
+ explicit BigBuffer(size_t block_size);
+
+ BigBuffer(BigBuffer&& rhs) noexcept;
+
+ /**
+ * Number of occupied bytes in all the allocated blocks.
+ */
+ size_t size() const;
+
+ /**
+ * Returns a pointer to an array of T, where T is
+ * a POD type. The elements are zero-initialized.
+ */
+ template <typename T>
+ T* NextBlock(size_t count = 1);
+
+ /**
+ * Returns the next block available and puts the size in out_count.
+ * This is useful for grabbing blocks where the size doesn't matter.
+ * Use BackUp() to give back any bytes that were not used.
+ */
+ void* NextBlock(size_t* out_count);
+
+ /**
+ * Backs up count bytes. This must only be called after NextBlock()
+ * and can not be larger than sizeof(T) * count of the last NextBlock()
+ * call.
+ */
+ void BackUp(size_t count);
+
+ /**
+ * Moves the specified BigBuffer into this one. When this method
+ * returns, buffer is empty.
+ */
+ void AppendBuffer(BigBuffer&& buffer);
+
+ /**
+ * Pads the block with 'bytes' bytes of zero values.
+ */
+ void Pad(size_t bytes);
+
+ /**
+ * Pads the block so that it aligns on a 4 byte boundary.
+ */
+ void Align4();
+
+ size_t block_size() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ std::string to_string() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BigBuffer);
+
+ /**
+ * Returns a pointer to a buffer of the requested size.
+ * The buffer is zero-initialized.
+ */
+ void* NextBlockImpl(size_t size);
+
+ size_t block_size_;
+ size_t size_;
+ std::vector<Block> blocks_;
+};
+
+inline BigBuffer::BigBuffer(size_t block_size) : block_size_(block_size), size_(0) {
+}
+
+inline BigBuffer::BigBuffer(BigBuffer&& rhs) noexcept
+ : block_size_(rhs.block_size_), size_(rhs.size_), blocks_(std::move(rhs.blocks_)) {
+}
+
+inline size_t BigBuffer::size() const {
+ return size_;
+}
+
+inline size_t BigBuffer::block_size() const {
+ return block_size_;
+}
+
+template <typename T>
+inline T* BigBuffer::NextBlock(size_t count) {
+ static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type");
+ CHECK(count != 0);
+ return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count));
+}
+
+inline void BigBuffer::BackUp(size_t count) {
+ Block& block = blocks_.back();
+ block.size -= count;
+ size_ -= count;
+}
+
+inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) {
+ std::move(buffer.blocks_.begin(), buffer.blocks_.end(), std::back_inserter(blocks_));
+ size_ += buffer.size_;
+ buffer.blocks_.clear();
+ buffer.size_ = 0;
+}
+
+inline void BigBuffer::Pad(size_t bytes) {
+ NextBlock<char>(bytes);
+}
+
+inline void BigBuffer::Align4() {
+ const size_t unaligned = size_ % 4;
+ if (unaligned != 0) {
+ Pad(4 - unaligned);
+ }
+}
+
+inline BigBuffer::const_iterator BigBuffer::begin() const {
+ return blocks_.begin();
+}
+
+inline BigBuffer::const_iterator BigBuffer::end() const {
+ return blocks_.end();
+}
+
+} // namespace android
+
+#endif // _ANDROID_BIG_BUFFER_H
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 949c9445b3e8..ca0a9eda9caa 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
#ifndef __BYTE_BUCKET_ARRAY_H
#define __BYTE_BUCKET_ARRAY_H
+#include <algorithm>
#include <cstdint>
#include <cstring>
@@ -31,14 +32,16 @@ namespace android {
template <typename T>
class ByteBucketArray {
public:
- ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+ ByteBucketArray() {
+ memset(buckets_, 0, sizeof(buckets_));
+ }
~ByteBucketArray() {
- for (size_t i = 0; i < kNumBuckets; i++) {
- if (buckets_[i] != NULL) {
- delete[] buckets_[i];
- }
- }
+ deleteBuckets();
+ }
+
+ void clear() {
+ deleteBuckets();
memset(buckets_, 0, sizeof(buckets_));
}
@@ -53,7 +56,7 @@ class ByteBucketArray {
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
+ if (bucket == nullptr) {
return default_;
}
return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -64,9 +67,9 @@ class ByteBucketArray {
<< ") with size=" << size();
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
- T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
- bucket = buckets_[bucket_index] = new T[kBucketSize]();
+ T*& bucket = buckets_[bucket_index];
+ if (bucket == nullptr) {
+ bucket = new T[kBucketSize]();
}
return bucket[0x0f & static_cast<uint8_t>(index)];
}
@@ -80,11 +83,44 @@ class ByteBucketArray {
return true;
}
+ template <class Func>
+ void forEachItem(Func f) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ for (size_t j = 0; j < kBucketSize; j++) {
+ f((i << 4) | j, bucket[j]);
+ }
+ }
+ }
+ }
+
+ template <class Func>
+ void trimBuckets(Func isEmptyFunc) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+ delete[] bucket;
+ buckets_[i] = nullptr;
+ }
+ }
+ }
+ }
+
private:
enum { kNumBuckets = 16, kBucketSize = 16 };
+ void deleteBuckets() {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ if (buckets_[i] != nullptr) {
+ delete[] buckets_[i];
+ }
+ }
+ }
+
T* buckets_[kNumBuckets];
- T default_;
+ static inline const T default_ = {};
};
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 61d10cd4e55b..7fbd7c08ea69 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -53,6 +53,12 @@ enum : ApiVersion {
SDK_O = 26,
SDK_O_MR1 = 27,
SDK_P = 28,
+ SDK_Q = 29,
+ SDK_R = 30,
+ SDK_S = 31,
+ SDK_S_V2 = 32,
+ SDK_TIRAMISU = 33,
+ SDK_U = 34,
};
/*
@@ -72,7 +78,7 @@ struct ConfigDescription : public ResTable_config {
* The resulting configuration has the appropriate sdkVersion defined
* for backwards compatibility.
*/
- static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+ static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr);
/**
* If the configuration uses an axis that was added after
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
new file mode 100644
index 000000000000..4d5844eaa069
--- /dev/null
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_DIAGNOSTICS_H
+#define _ANDROID_DIAGNOSTICS_H
+
+#include <sstream>
+#include <string>
+
+#include "Source.h"
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+struct DiagMessageActual {
+ Source source;
+ std::string message;
+};
+
+struct DiagMessage {
+ public:
+ DiagMessage() = default;
+
+ explicit DiagMessage(android::StringPiece src) : source_(src) {
+ }
+
+ explicit DiagMessage(const Source& src) : source_(src) {
+ }
+
+ explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {
+ }
+
+ template <typename T>
+ DiagMessage& operator<<(const T& value) {
+ message_ << value;
+ return *this;
+ }
+
+ DiagMessageActual Build() const {
+ return DiagMessageActual{source_, message_.str()};
+ }
+
+ private:
+ Source source_;
+ std::stringstream message_;
+};
+
+template <>
+inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
+ message_ << value;
+ return *this;
+}
+
+struct IDiagnostics {
+ virtual ~IDiagnostics() = default;
+
+ enum class Level { Note, Warn, Error };
+
+ virtual void Log(Level level, DiagMessageActual& actualMsg) = 0;
+
+ virtual void Error(const DiagMessage& message) {
+ DiagMessageActual actual = message.Build();
+ Log(Level::Error, actual);
+ }
+
+ virtual void Warn(const DiagMessage& message) {
+ DiagMessageActual actual = message.Build();
+ Log(Level::Warn, actual);
+ }
+
+ virtual void Note(const DiagMessage& message) {
+ DiagMessageActual actual = message.Build();
+ Log(Level::Note, actual);
+ }
+};
+
+class SourcePathDiagnostics : public IDiagnostics {
+ public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : source_(src), diag_(diag) {
+ }
+
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ actual_msg.source.path = source_.path;
+ diag_->Log(level, actual_msg);
+ if (level == Level::Error) {
+ error = true;
+ }
+ }
+
+ bool HadError() {
+ return error;
+ }
+
+ private:
+ Source source_;
+ IDiagnostics* diag_;
+ bool error = false;
+
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
+};
+
+class NoOpDiagnostics : public IDiagnostics {
+ public:
+ NoOpDiagnostics() = default;
+
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ (void)level;
+ (void)actual_msg;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NoOpDiagnostics);
+};
+
+} // namespace android
+
+#endif /* _ANDROID_DIAGNOSTICS_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6804472b3d17..60689128dffb 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -23,6 +23,7 @@
#include <variant>
#include "android-base/macros.h"
+#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
#include "androidfw/ResourceTypes.h"
#include "utils/ByteOrder.h"
@@ -35,6 +36,7 @@ struct Idmap_header;
struct Idmap_data_header;
struct Idmap_target_entry;
struct Idmap_target_entry_inline;
+struct Idmap_target_entry_inline_value;
struct Idmap_overlay_entry;
// A string pool for overlay apk assets. The string pool holds the strings of the overlay resources
@@ -91,7 +93,8 @@ class IdmapResMap {
public:
Result() = default;
explicit Result(uint32_t value) : data_(value) {};
- explicit Result(const Res_value& value) : data_(value) { };
+ explicit Result(std::map<ConfigDescription, Res_value> value) : data_(std::move(value)) {
+ }
// Returns `true` if the resource is overlaid.
explicit operator bool() const {
@@ -107,15 +110,16 @@ class IdmapResMap {
}
bool IsInlineValue() const {
- return std::get_if<Res_value>(&data_) != nullptr;
+ return std::get_if<2>(&data_) != nullptr;
}
- const Res_value& GetInlineValue() const {
- return std::get<Res_value>(data_);
+ const std::map<ConfigDescription, Res_value>& GetInlineValue() const {
+ return std::get<2>(data_);
}
private:
- std::variant<std::monostate, uint32_t, Res_value> data_;
+ std::variant<std::monostate, uint32_t,
+ std::map<ConfigDescription, Res_value> > data_;
};
// Looks up the value that overlays the target resource id.
@@ -129,12 +133,16 @@ class IdmapResMap {
explicit IdmapResMap(const Idmap_data_header* data_header,
const Idmap_target_entry* entries,
const Idmap_target_entry_inline* inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ const ConfigDescription* configs,
uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table);
const Idmap_data_header* data_header_;
const Idmap_target_entry* entries_;
const Idmap_target_entry_inline* inline_entries_;
+ const Idmap_target_entry_inline_value* inline_entry_values_;
+ const ConfigDescription* configurations_;
const uint8_t target_assigned_package_id_;
const OverlayDynamicRefTable* overlay_ref_table_;
@@ -149,8 +157,7 @@ class IdmapResMap {
class LoadedIdmap {
public:
// Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
- static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data);
+ static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
// Returns the path to the IDMAP.
std::string_view IdmapPath() const {
@@ -170,8 +177,8 @@ class LoadedIdmap {
// Returns a mapping from target resource ids to overlay values.
const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table) const {
- return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
- target_assigned_package_id, overlay_ref_table);
+ return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_,
+ configurations_, target_assigned_package_id, overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
@@ -191,6 +198,8 @@ class LoadedIdmap {
const Idmap_data_header* data_header_;
const Idmap_target_entry* target_entries_;
const Idmap_target_entry_inline* target_inline_entries_;
+ const Idmap_target_entry_inline_value* inline_entry_values_;
+ const ConfigDescription* configurations_;
const Idmap_overlay_entry* overlay_entries_;
const std::unique_ptr<ResStringPool> string_pool_;
@@ -207,6 +216,8 @@ class LoadedIdmap {
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
const Idmap_target_entry_inline* target_inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values_,
+ const ConfigDescription* configs,
const Idmap_overlay_entry* overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
std::string_view overlay_apk_path,
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index b3d6a4dcb955..4d12885ad291 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@ enum : package_property_t {
};
struct OverlayableInfo {
- std::string name;
- std::string actor;
+ std::string_view name;
+ std::string_view actor;
uint32_t policy_flags;
};
@@ -166,14 +166,14 @@ class LoadedPackage {
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
@@ -275,7 +275,7 @@ class LoadedPackage {
return overlayable_map_;
}
- const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+ const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
return alias_id_map_;
}
@@ -295,8 +295,8 @@ class LoadedPackage {
std::unordered_map<uint8_t, TypeSpec> type_specs_;
ByteBucketArray<uint32_t> resource_ids_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
- std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
- std::map<uint32_t, uint32_t> alias_id_map_;
+ std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+ std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
// A map of overlayable name to actor
std::unordered_map<std::string, std::string> overlayable_map_;
@@ -314,6 +314,8 @@ class LoadedArsc {
const LoadedIdmap* loaded_idmap = nullptr,
package_property_t property_flags = 0U);
+ static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr);
+
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<LoadedArsc> CreateEmpty();
@@ -338,6 +340,7 @@ class LoadedArsc {
LoadedArsc() = default;
bool LoadTable(
const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags);
+ bool LoadStringPool(const LoadedIdmap* loaded_idmap);
std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>();
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
index 484ed79a8efd..8934bed098fe 100644
--- a/libs/androidfw/include/androidfw/Locale.h
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -39,10 +39,10 @@ struct LocaleValue {
/**
* Initialize this LocaleValue from a config string.
*/
- bool InitFromFilterString(const android::StringPiece& config);
+ bool InitFromFilterString(android::StringPiece config);
// Initializes this LocaleValue from a BCP-47 locale tag.
- bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+ bool InitFromBcp47Tag(android::StringPiece bcp47tag);
/**
* Initialize this LocaleValue from parts of a vector.
@@ -70,7 +70,7 @@ struct LocaleValue {
inline bool operator>(const LocaleValue& o) const;
private:
- bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+ bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator);
void set_language(const char* language);
void set_region(const char* language);
diff --git a/libs/androidfw/include/androidfw/PosixUtils.h b/libs/androidfw/include/androidfw/PosixUtils.h
index bb2084740a44..c46e5e6b3fb5 100644
--- a/libs/androidfw/include/androidfw/PosixUtils.h
+++ b/libs/androidfw/include/androidfw/PosixUtils.h
@@ -25,12 +25,18 @@ struct ProcResult {
int status;
std::string stdout_str;
std::string stderr_str;
+
+ explicit ProcResult(int status) : status(status) {}
+ ProcResult(ProcResult&&) noexcept = default;
+ ProcResult& operator=(ProcResult&&) noexcept = default;
+
+ explicit operator bool() const { return status >= 0; }
};
-// Fork, exec and wait for an external process. Return nullptr if the process could not be launched,
-// otherwise a ProcResult containing the external process' exit status and captured stdout and
-// stderr.
-std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv);
+// Fork, exec and wait for an external process. Returns status < 0 if the process could not be
+// launched, otherwise a ProcResult containing the external process' exit status and captured
+// stdout and stderr.
+ProcResult ExecuteBinary(const std::vector<std::string>& argv);
} // namespace util
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ResourceTimer.h b/libs/androidfw/include/androidfw/ResourceTimer.h
new file mode 100644
index 000000000000..74613519a920
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ResourceTimer.h
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROIDFW_RESOURCETIMER_H_
+#define ANDROIDFW_RESOURCETIMER_H_
+
+#include <time.h>
+#include <atomic>
+#include <vector>
+
+#include <utils/Mutex.h>
+#include <android-base/macros.h>
+#include <androidfw/Util.h>
+
+namespace android {
+
+// ResourceTimer captures the duration of short functions. Durations are accumulated in registers
+// and statistics are pulled back to the Java layer as needed.
+// To monitor an API, first add it to the Counter enumeration. Then, inside the API, create an
+// instance of ResourceTimer with the appropriate enumeral. The corresponding counter will be
+// updated when the ResourceTimer destructor is called, normally at the end of the enclosing block.
+class ResourceTimer {
+ public:
+ enum class Counter {
+ GetResourceValue,
+ RetrieveAttributes,
+
+ LastCounter = RetrieveAttributes,
+ };
+ static const int counterSize = static_cast<int>(Counter::LastCounter) + 1;
+ static char const *toString(Counter);
+
+ // Start a timer for the specified counter.
+ ResourceTimer(Counter);
+ // The block is exiting. If the timer is active, record it.
+ ~ResourceTimer();
+ // This records the elapsed time and disables further recording. Use this if the containing
+ // block includes extra processing that should not be included in the timer. The method is
+ // destructive in that the timer is no longer valid and further calls to record() will be
+ // ignored.
+ void record();
+ // This cancels a timer. Elapsed time will neither be computed nor recorded.
+ void cancel();
+
+ // A single timer contains the count of events and the cumulative time spent handling the
+ // events. It also includes the smallest value seen and 10 largest values seen. Finally, it
+ // includes a histogram of values that approximates a semi-log.
+
+ // The timer can compute percentiles of recorded events. For example, the p50 value is a time
+ // such that 50% of the readings are below the value and 50% are above the value. The
+ // granularity in the readings means that a percentile cannot always be computed. In this case,
+ // the percentile is reported as zero. (The simplest example is when there is a single
+ // reading.) Even if the value can be computed, it will not be exact. Therefore, a percentile
+ // is actually reported as two values: the lowest time at which it might be valid and the
+ // highest time at which it might be valid.
+ struct Timer {
+ static const size_t MaxLargest = 5;
+
+ // The construct zeros all the fields. The destructor releases memory allocated to the
+ // buckets.
+ Timer();
+ ~Timer();
+
+ // The following summary values are set to zero on a reset. All times are in ns.
+
+ // The total number of events recorded.
+ int count;
+ // The total duration of events.
+ int64_t total;
+ // The smallest event duration seen. This is guaranteed to be non-zero if count is greater
+ // than 0.
+ int mintime;
+ // The largest event duration seen.
+ int maxtime;
+
+ // The largest values seen. Element 0 is the largest value seen (and is the same as maxtime,
+ // above). Element 1 is the next largest, and so on. If count is less than MaxLargest,
+ // unused elements will be zero.
+ int largest[MaxLargest];
+
+ // The p50 value is a time such that 50% of the readings are below that time and 50% of the
+ // readings.
+
+ // A single percentile is defined by the lowest value supported by the readings and the
+ // highest value supported by the readings.
+ struct Percentile {
+ // The nominal time (in ns) of the percentile. The true percentile is guaranteed to be less
+ // than or equal to this time.
+ int nominal;
+ // The actual percentile of the nominal time.
+ int nominal_actual;
+ // The time of the next lower bin. The true percentile is guaranteed to be greater than
+ // this time.
+ int floor;
+ // The actual percentile of the floor time.
+ int floor_actual;
+
+ // Fill in a percentile given the cumulative to the bin, the count in the current bin, the
+ // total count, the width of the bin, and the time of the bin.
+ void compute(int cumulative, int current, int count, int width, int time);
+ };
+
+ // The structure that holds the percentiles.
+ struct {
+ Percentile p50;
+ Percentile p90;
+ Percentile p95;
+ Percentile p99;
+ } pvalues;
+
+ // Set all counters to zero.
+ void reset();
+ // Record an event. The input time is in ns.
+ void record(int);
+ // Compute the percentiles. Percentiles are computed on demand, as the computation is too
+ // expensive to be done inline.
+ void compute();
+
+ // Copy one timer to another. If reset is true then the src is reset immediately after the
+ // copy. The reset flag is exploited to make the copy faster. Any data in dst is lost.
+ static void copy(Timer &dst, Timer &src, bool reset);
+
+ private:
+ // Free any buckets.
+ void freeBuckets();
+
+ // Readings are placed in bins, which are orgzanized into decades. The decade 0 covers
+ // [0,100) in steps of 1us. Decade 1 covers [0,1000) in steps of 10us. Decade 2 covers
+ // [0,10000) in steps of 100us. And so on.
+
+ // An event is placed in the first bin that can hold it. This means that events in the range
+ // of [0,100) are placed in the first decade, events in the range of [0,1000) are placed in
+ // the second decade, and so on. This also means that the first 10% of the bins are unused
+ // in each decade after the first.
+
+ // The design provides at least two significant digits across the range of [0,10000).
+
+ static const size_t MaxDimension = 4;
+ static const size_t MaxBuckets = 100;
+
+ // The range of each dimension. The lower value is always zero.
+ static const int range[MaxDimension];
+ // The width of each bin, by dimension
+ static const int width[MaxDimension];
+
+ // A histogram of the values seen. Centuries are allocated as needed, to minimize the memory
+ // impact.
+ int *buckets[MaxDimension];
+ };
+
+ // Fetch one Timer. The function has a short-circuit behavior: if the count is zero then
+ // destination count is set to zero and the function returns false. Otherwise, the destination
+ // is a copy of the source and the function returns true. This behavior lowers the cost of
+ // handling unused timers.
+ static bool copy(int src, Timer &dst, bool reset);
+
+ // Enable the timers. Timers are initially disabled. Enabling timers allocates memory for the
+ // counters. Timers cannot be disabled.
+ static void enable();
+
+ private:
+ // An internal reset method. This does not take a lock.
+ static void reset();
+
+ // Helper method to convert a counter into an enum. Presumably, this will be inlined into zero
+ // actual cpu instructions.
+ static inline std::vector<unsigned int>::size_type toIndex(Counter c) {
+ return static_cast<std::vector<unsigned int>::size_type>(c);
+ }
+
+ // Every counter has an associated lock. The lock has been factored into a separate class to
+ // keep the Timer class a POD.
+ struct GuardedTimer {
+ Mutex lock_;
+ Timer timer_;
+ };
+
+ // Scoped timer
+ struct ScopedTimer {
+ AutoMutex _l;
+ Timer &t;
+ ScopedTimer(GuardedTimer &g) :
+ _l(g.lock_), t(g.timer_) {
+ }
+ Timer *operator->() {
+ return &t;
+ }
+ Timer& operator*() {
+ return t;
+ }
+ };
+
+ // An individual timer is active (or not), is tracking a specific API, and has a start time.
+ // The api and the start time are undefined if the timer is not active.
+ bool active_;
+ Counter api_;
+ struct timespec start_;
+
+ // The global enable flag. This is initially false and may be set true by the java runtime.
+ static std::atomic<bool> enabled_;
+
+ // The global timers. The memory for the timers is not allocated until the timers are enabled.
+ static std::atomic<GuardedTimer *> counter_;
+};
+
+} // namespace android
+
+#endif /* ANDROIDFW_RESOURCETIMER_H_ */
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 3d66244646d5..631bda4f886c 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,11 +21,13 @@
#define _LIBS_UTILS_RESOURCE_TYPES_H
#include <android-base/expected.h>
+#include <android-base/unique_fd.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -45,7 +47,7 @@
namespace android {
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u;
// This must never change.
constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
@@ -53,10 +55,12 @@ constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endi
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1067,15 +1071,32 @@ struct ResTable_config
NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN,
NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN,
};
-
- union {
- struct {
- uint8_t keyboard;
- uint8_t navigation;
- uint8_t inputFlags;
- uint8_t inputPad0;
+
+ enum {
+ GRAMMATICAL_GENDER_ANY = ACONFIGURATION_GRAMMATICAL_GENDER_ANY,
+ GRAMMATICAL_GENDER_NEUTER = ACONFIGURATION_GRAMMATICAL_GENDER_NEUTER,
+ GRAMMATICAL_GENDER_FEMININE = ACONFIGURATION_GRAMMATICAL_GENDER_FEMININE,
+ GRAMMATICAL_GENDER_MASCULINE = ACONFIGURATION_GRAMMATICAL_GENDER_MASCULINE,
+ GRAMMATICAL_INFLECTION_GENDER_MASK = 0b11,
+ };
+
+ struct {
+ union {
+ struct {
+ uint8_t keyboard;
+ uint8_t navigation;
+ uint8_t inputFlags;
+ uint8_t inputFieldPad0;
+ };
+ struct {
+ uint32_t input : 24;
+ uint32_t inputFullPad0 : 8;
+ };
+ struct {
+ uint8_t grammaticalInflectionPad0[3];
+ uint8_t grammaticalInflection;
+ };
};
- uint32_t input;
};
enum {
@@ -1098,7 +1119,7 @@ struct ResTable_config
SDKVERSION_ANY = 0
};
- enum {
+ enum {
MINORVERSION_ANY = 0
};
@@ -1259,6 +1280,7 @@ struct ResTable_config
CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
+ CONFIG_GRAMMATICAL_GENDER = ACONFIGURATION_GRAMMATICAL_GENDER,
};
// Compare two configuration, returning CONFIG_* flags set for each value
@@ -1437,6 +1459,10 @@ struct ResTable_type
// Mark any types that use this with a v26 qualifier to prevent runtime issues on older
// platforms.
FLAG_SPARSE = 0x01,
+
+ // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
+ // An 16-bit offset of 0xffffu means a NO_ENTRY
+ FLAG_OFFSET16 = 0x02,
};
uint8_t flags;
@@ -1453,6 +1479,11 @@ struct ResTable_type
ResTable_config config;
};
+// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set
+static inline uint32_t offset_from16(uint16_t off16) {
+ return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u;
+}
+
// The minimum size required to read any version of ResTable_type.
constexpr size_t kResTableTypeMinSize =
sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size);
@@ -1480,6 +1511,8 @@ union ResTable_sparseTypeEntry {
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
+struct ResTable_map_entry;
+
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
@@ -1487,12 +1520,11 @@ static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
* * A Res_value structure, if FLAG_COMPLEX is -not- set.
* * An array of ResTable_map structures, if FLAG_COMPLEX is set.
* These supply a set of name/value mappings of data.
+ * * If FLAG_COMPACT is set, this entry is a compact entry for
+ * simple values only
*/
-struct ResTable_entry
+union ResTable_entry
{
- // Number of bytes in this structure.
- uint16_t size;
-
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
@@ -1504,18 +1536,91 @@ struct ResTable_entry
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004,
+ // If set, this is a compact entry with data type and value directly
+ // encoded in the this entry, see ResTable_entry::compact
+ FLAG_COMPACT = 0x0008,
};
- uint16_t flags;
-
- // Reference into ResTable_package::keyStrings identifying this entry.
- struct ResStringPool_ref key;
+
+ struct Full {
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+ } full;
+
+ /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+ * offset as a normal entry. This is only for simple data values where
+ *
+ * - size for entry or value can be inferred (both being 8 bytes).
+ * - key index is encoded in 16-bit
+ * - dataType is encoded as the higher 8-bit of flags
+ * - data is encoded directly in this entry
+ */
+ struct Compact {
+ uint16_t key;
+ uint16_t flags;
+ uint32_t data;
+ } compact;
+
+ uint16_t flags() const { return dtohs(full.flags); };
+ bool is_compact() const { return flags() & FLAG_COMPACT; }
+ bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+ size_t size() const {
+ return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+ }
+
+ uint32_t key() const {
+ return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+ }
+
+ /* Always verify the memory associated with this entry and its value
+ * before calling value() or map_entry()
+ */
+ Res_value value() const {
+ Res_value v;
+ if (is_compact()) {
+ v.size = sizeof(Res_value);
+ v.res0 = 0;
+ v.data = dtohl(this->compact.data);
+ v.dataType = dtohs(compact.flags) >> 8;
+ } else {
+ auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+ auto value = reinterpret_cast<const Res_value*>(vaddr);
+ v.size = dtohs(value->size);
+ v.res0 = value->res0;
+ v.data = dtohl(value->data);
+ v.dataType = value->dataType;
+ }
+ return v;
+ }
+
+ const ResTable_map_entry* map_entry() const {
+ return is_complex() && !is_compact() ?
+ reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+ }
};
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
/**
* Extended form of a ResTable_entry for map entries, defining a parent map
* resource from which to inherit values.
*/
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
@@ -1746,6 +1851,28 @@ inline ResTable_overlayable_policy_header::PolicyFlags& operator |=(
return first;
}
+using ResourceId = uint32_t; // 0xpptteeee
+
+using DataType = uint8_t; // Res_value::dataType
+using DataValue = uint32_t; // Res_value::data
+
+struct OverlayManifestInfo {
+ std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
+ ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+struct FabricatedOverlayEntryParameters {
+ std::string resource_name;
+ DataType data_type;
+ DataValue data_value;
+ std::string data_string_value;
+ std::optional<android::base::borrowed_fd> data_binary_value;
+ std::string configuration;
+};
+
class AssetManager2;
/**
@@ -1776,7 +1903,10 @@ public:
void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
- void addAlias(uint32_t stagedId, uint32_t finalizedId);
+ using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
+ void setAliases(AliasMap aliases) {
+ mAliasId = std::move(aliases);
+ }
// Returns whether or not the value must be looked up.
bool requiresLookup(const Res_value* value) const;
@@ -1790,12 +1920,12 @@ public:
return mEntries;
}
-private:
- uint8_t mAssignedPackageId;
- uint8_t mLookupTable[256];
- KeyedVector<String16, uint8_t> mEntries;
- bool mAppAsLib;
- std::map<uint32_t, uint32_t> mAliasId;
+ private:
+ uint8_t mLookupTable[256];
+ uint8_t mAssignedPackageId;
+ bool mAppAsLib;
+ KeyedVector<String16, uint8_t> mEntries;
+ AliasMap mAliasId;
};
bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index bd1c44033b88..2d90a526dfbe 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -25,14 +25,14 @@ namespace android {
// Extracts the package, type, and name from a string of the format: [[package:]type/]name
// Validation must be performed on each extracted piece.
// Returns false if there was a syntax error.
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry);
// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
// Useful for getting resource name without re-running AssetManager2::FindEntry searches.
base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
- const StringPiece& package_name);
+ StringPiece package_name);
// Formats a ResourceName to "package:type/entry_name".
std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name);
diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h
new file mode 100644
index 000000000000..ddc9ba421101
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Source.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SOURCE_H
+#define _ANDROID_SOURCE_H
+
+#include <optional>
+#include <ostream>
+#include <string>
+
+#include "android-base/stringprintf.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+// Represents a file on disk. Used for logging and showing errors.
+struct Source {
+ std::string path;
+ std::optional<size_t> line;
+ std::optional<std::string> archive;
+
+ Source() = default;
+
+ inline Source(android::StringPiece path) : path(path) { // NOLINT(implicit)
+ }
+
+ inline Source(android::StringPiece path, android::StringPiece archive)
+ : path(path), archive(archive) {
+ }
+
+ inline Source(android::StringPiece path, size_t line) : path(path), line(line) {
+ }
+
+ inline Source WithLine(size_t line) const {
+ return Source(path, line);
+ }
+
+ std::string to_string() const {
+ std::string s = path;
+ if (archive) {
+ s = ::android::base::StringPrintf("%s@%s", archive.value().c_str(), s.c_str());
+ }
+ if (line) {
+ s = ::android::base::StringPrintf("%s:%zd", s.c_str(), line.value());
+ }
+ return s;
+ }
+};
+
+//
+// Implementations
+//
+
+inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
+ return out << source.to_string();
+}
+
+inline bool operator==(const Source& lhs, const Source& rhs) {
+ return lhs.path == rhs.path && lhs.line == rhs.line;
+}
+
+inline bool operator<(const Source& lhs, const Source& rhs) {
+ int cmp = lhs.path.compare(rhs.path);
+ if (cmp < 0) return true;
+ if (cmp > 0) return false;
+ if (lhs.line) {
+ if (rhs.line) {
+ return lhs.line.value() < rhs.line.value();
+ }
+ return false;
+ }
+ return bool(rhs.line);
+}
+
+} // namespace android
+
+#endif // _ANDROID_SOURCE_H
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index 921877dc4982..f6cc64edfb5a 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -19,307 +19,37 @@
#include <ostream>
#include <string>
+#include <string_view>
-#include "utils/JenkinsHash.h"
#include "utils/Unicode.h"
namespace android {
-// Read only wrapper around basic C strings. Prevents excessive copying.
-// StringPiece does not own the data it is wrapping. The lifetime of the underlying
-// data must outlive this StringPiece.
-//
-// WARNING: When creating from std::basic_string<>, moving the original
-// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
-// BasicStringPiece<> should only be used transitively.
-//
-// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(),
-// passing an std::string will first copy the string, then create a StringPiece
-// on the copy, which is then immediately destroyed.
-// Instead, create a StringPiece explicitly:
-//
-// std::string my_string = "foo";
-// std::make_pair<StringPiece, T>(StringPiece(my_string), ...);
-template <typename TChar>
-class BasicStringPiece {
- public:
- using const_iterator = const TChar*;
- using difference_type = size_t;
- using size_type = size_t;
-
- // End of string marker.
- constexpr static const size_t npos = static_cast<size_t>(-1);
-
- BasicStringPiece();
- BasicStringPiece(const BasicStringPiece<TChar>& str);
- BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(google-explicit-constructor)
- BasicStringPiece(const TChar* str); // NOLINT(google-explicit-constructor)
- BasicStringPiece(const TChar* str, size_t len);
-
- BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
- BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
- BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
- BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
-
- const TChar* data() const;
- size_t length() const;
- size_t size() const;
- bool empty() const;
- std::basic_string<TChar> to_string() const;
-
- bool contains(const BasicStringPiece<TChar>& rhs) const;
- int compare(const BasicStringPiece<TChar>& rhs) const;
- bool operator<(const BasicStringPiece<TChar>& rhs) const;
- bool operator>(const BasicStringPiece<TChar>& rhs) const;
- bool operator==(const BasicStringPiece<TChar>& rhs) const;
- bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
- const_iterator begin() const;
- const_iterator end() const;
-
- private:
- const TChar* data_;
- size_t length_;
-};
+template <class T>
+using BasicStringPiece = std::basic_string_view<T>;
using StringPiece = BasicStringPiece<char>;
using StringPiece16 = BasicStringPiece<char16_t>;
-//
-// BasicStringPiece implementation.
-//
-
-template <typename TChar>
-constexpr const size_t BasicStringPiece<TChar>::npos;
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
- : data_(str.data_), length_(str.length_) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
- : data_(str.data()), length_(str.length()) {}
-
-template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str)
- : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
-
-template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
- : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
- : data_(str), length_(len) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
- const BasicStringPiece<TChar>& rhs) {
- data_ = rhs.data_;
- length_ = rhs.length_;
- return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
- data_ = str;
- length_ = len;
- return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
- if (len == npos) {
- len = length_ - start;
- }
-
- if (start > length_ || start + len > length_) {
- return BasicStringPiece<TChar>();
- }
- return BasicStringPiece<TChar>(data_ + start, len);
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
- BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const {
- return BasicStringPiece<TChar>(begin, end - begin);
-}
-
-template <typename TChar>
-inline const TChar* BasicStringPiece<TChar>::data() const {
- return data_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::length() const {
- return length_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::size() const {
- return length_;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::empty() const {
- return length_ == 0;
-}
-
-template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
- return std::basic_string<TChar>(data_, length_);
-}
-
-template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
- if (!data_ || !rhs.data_) {
- return false;
- }
- if (rhs.length_ > length_) {
- return false;
- }
- return strstr(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
- const char nullStr = '\0';
- const char* b1 = data_ != nullptr ? data_ : &nullStr;
- const char* e1 = b1 + length_;
- const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
- const char* e2 = b2 + rhs.length_;
-
- while (b1 < e1 && b2 < e2) {
- const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
- if (d) {
- return d;
- }
- }
- return static_cast<int>(length_ - rhs.length_);
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
- const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
- if (result_len < 0) {
- // Empty string.
- return out;
- }
-
- std::string result;
- result.resize(static_cast<size_t>(result_len));
- utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
- return out << result;
-}
-
-template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
- if (!data_ || !rhs.data_) {
- return false;
- }
- if (rhs.length_ > length_) {
- return false;
- }
- return strstr16(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
- const char16_t nullStr = u'\0';
- const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
- const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
- return strzcmp16(b1, length_, b2, rhs.length_);
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) < 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) > 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) == 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) != 0;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
- return data_;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
- return data_ + length_;
-}
-
-template <typename TChar>
-inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
- return out.write(str.data(), str.size());
-}
-
-template <typename TChar>
-inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs,
- const BasicStringPiece<TChar>& rhs) {
- return lhs.append(rhs.data(), rhs.size());
-}
-
-template <typename TChar>
-inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
- return rhs == lhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
- return rhs != lhs;
-}
-
} // namespace android
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+namespace std {
+
+inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) {
ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size());
if (utf8_len < 0) {
- return out << "???";
+ return out; // empty
}
std::string utf8;
utf8.resize(static_cast<size_t>(utf8_len));
- utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1);
+ utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1);
return out << utf8;
}
-namespace std {
-
-template <typename TChar>
-struct hash<android::BasicStringPiece<TChar>> {
- size_t operator()(const android::BasicStringPiece<TChar>& str) const {
- uint32_t hashCode = android::JenkinsHashMixBytes(
- 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
- return static_cast<size_t>(hashCode);
- }
-};
+inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) {
+ return out << std::u16string_view(str);
+}
} // namespace std
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
new file mode 100644
index 000000000000..0190ab57bf23
--- /dev/null
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_STRING_POOL_H
+#define _ANDROID_STRING_POOL_H
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "BigBuffer.h"
+#include "IDiagnostics.h"
+#include "android-base/macros.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+struct Span {
+ std::string name;
+ uint32_t first_char;
+ uint32_t last_char;
+
+ bool operator==(const Span& right) const {
+ return name == right.name && first_char == right.first_char && last_char == right.last_char;
+ }
+};
+
+struct StyleString {
+ std::string str;
+ std::vector<Span> spans;
+};
+
+// A StringPool for storing the value of String and StyledString resources.
+// Styles and Strings are stored separately, since the runtime variant of this
+// class -- ResStringPool -- requires that styled strings *always* appear first, since their
+// style data is stored as an array indexed by the same indices as the main string pool array.
+// Otherwise, the style data array would have to be sparse and take up more space.
+class StringPool {
+ public:
+ using size_type = size_t;
+
+ class Context {
+ public:
+ enum : uint32_t {
+ kHighPriority = 1u,
+ kNormalPriority = 0x7fffffffu,
+ kLowPriority = 0xffffffffu,
+ };
+ uint32_t priority = kNormalPriority;
+ android::ConfigDescription config;
+
+ Context() = default;
+ Context(uint32_t p, const android::ConfigDescription& c) : priority(p), config(c) {
+ }
+ explicit Context(uint32_t p) : priority(p) {
+ }
+ explicit Context(const android::ConfigDescription& c) : priority(kNormalPriority), config(c) {
+ }
+ };
+
+ class Entry;
+
+ class Ref {
+ public:
+ Ref();
+ Ref(const Ref&);
+ ~Ref();
+
+ Ref& operator=(const Ref& rhs);
+ bool operator==(const Ref& rhs) const;
+ bool operator!=(const Ref& rhs) const;
+ const std::string* operator->() const;
+ const std::string& operator*() const;
+
+ size_t index() const;
+ const Context& GetContext() const;
+
+ private:
+ friend class StringPool;
+
+ explicit Ref(Entry* entry);
+
+ Entry* entry_;
+ };
+
+ class StyleEntry;
+
+ class StyleRef {
+ public:
+ StyleRef();
+ StyleRef(const StyleRef&);
+ ~StyleRef();
+
+ StyleRef& operator=(const StyleRef& rhs);
+ bool operator==(const StyleRef& rhs) const;
+ bool operator!=(const StyleRef& rhs) const;
+ const StyleEntry* operator->() const;
+ const StyleEntry& operator*() const;
+
+ size_t index() const;
+ const Context& GetContext() const;
+
+ private:
+ friend class StringPool;
+
+ explicit StyleRef(StyleEntry* entry);
+
+ StyleEntry* entry_;
+ };
+
+ class Entry {
+ public:
+ std::string value;
+ Context context;
+
+ private:
+ friend class StringPool;
+ friend class Ref;
+
+ size_t index_;
+ int ref_;
+ const StringPool* pool_;
+ };
+
+ struct Span {
+ Ref name;
+ uint32_t first_char;
+ uint32_t last_char;
+ };
+
+ class StyleEntry {
+ public:
+ std::string value;
+ Context context;
+ std::vector<Span> spans;
+
+ private:
+ friend class StringPool;
+ friend class StyleRef;
+
+ size_t index_;
+ int ref_;
+ };
+
+ static bool FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag);
+ static bool FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag);
+
+ StringPool() = default;
+ StringPool(StringPool&&) = default;
+ StringPool& operator=(StringPool&&) = default;
+
+ // Adds a string to the pool, unless it already exists. Returns a reference to the string in the
+ // pool.
+ Ref MakeRef(android::StringPiece str);
+
+ // Adds a string to the pool, unless it already exists, with a context object that can be used
+ // when sorting the string pool. Returns a reference to the string in the pool.
+ Ref MakeRef(android::StringPiece str, const Context& context);
+
+ // Adds a string from another string pool. Returns a reference to the string in the string pool.
+ Ref MakeRef(const Ref& ref);
+
+ // Adds a style to the string pool and returns a reference to it.
+ StyleRef MakeRef(const StyleString& str);
+
+ // Adds a style to the string pool with a context object that can be used when sorting the string
+ // pool. Returns a reference to the style in the string pool.
+ StyleRef MakeRef(const StyleString& str, const Context& context);
+
+ // Adds a style from another string pool. Returns a reference to the style in the string pool.
+ StyleRef MakeRef(const StyleRef& ref);
+
+ // Moves pool into this one without coalescing strings. When this function returns, pool will be
+ // empty.
+ void Merge(StringPool&& pool);
+
+ inline const std::vector<std::unique_ptr<Entry>>& strings() const {
+ return strings_;
+ }
+
+ // Returns the number of strings in the table.
+ inline size_t size() const {
+ return styles_.size() + strings_.size();
+ }
+
+ // Reserves space for strings and styles as an optimization.
+ void HintWillAdd(size_t string_count, size_t style_count);
+
+ // Sorts the strings according to their Context using some comparison function.
+ // Equal Contexts are further sorted by string value, lexicographically.
+ // If no comparison function is provided, values are only sorted lexicographically.
+ void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr);
+
+ // Removes any strings that have no references.
+ void Prune();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringPool);
+
+ static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
+
+ Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique);
+ void ReAssignIndices();
+
+ std::vector<std::unique_ptr<Entry>> strings_;
+ std::vector<std::unique_ptr<StyleEntry>> styles_;
+ std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_;
+};
+
+} // namespace android
+
+#endif // _ANDROID_STRING_POOL_H
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index c59b5b6c51a2..a188abb7ecb5 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -17,20 +17,29 @@
#ifndef UTIL_H_
#define UTIL_H_
+#include <android-base/macros.h>
+#include <util/map_ptr.h>
+
#include <cstdlib>
#include <memory>
-#include <sstream>
#include <vector>
-#include <android-base/macros.h>
-#include <util/map_ptr.h>
-
+#include "androidfw/BigBuffer.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
+#include "utils/ByteOrder.h"
#ifdef __ANDROID__
#define ANDROID_LOG(x) LOG(x)
#else
-#define ANDROID_LOG(x) std::stringstream()
+namespace android {
+// No default logging for aapt2, as it's too noisy for a command line dev tool.
+struct NullLogger {
+ template <class T>
+ friend const NullLogger& operator<<(const NullLogger& l, const T&) { return l; }
+};
+}
+#define ANDROID_LOG(x) (android::NullLogger{})
#endif
namespace android {
@@ -46,95 +55,67 @@ std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
-// Based on std::unique_ptr, but uses free() to release malloc'ed memory
-// without incurring the size increase of holding on to a custom deleter.
-template <typename T>
-class unique_cptr {
- public:
- using pointer = typename std::add_pointer<T>::type;
-
- constexpr unique_cptr() : ptr_(nullptr) {}
- constexpr explicit unique_cptr(std::nullptr_t) : ptr_(nullptr) {}
- explicit unique_cptr(pointer ptr) : ptr_(ptr) {}
- unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; }
-
- ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); }
-
- inline unique_cptr& operator=(unique_cptr&& o) noexcept {
- if (&o == this) {
- return *this;
- }
-
- std::free(reinterpret_cast<void*>(ptr_));
- ptr_ = o.ptr_;
- o.ptr_ = nullptr;
- return *this;
- }
-
- inline unique_cptr& operator=(std::nullptr_t) {
- std::free(reinterpret_cast<void*>(ptr_));
- ptr_ = nullptr;
- return *this;
- }
-
- pointer release() {
- pointer result = ptr_;
- ptr_ = nullptr;
- return result;
- }
-
- inline pointer get() const { return ptr_; }
-
- void reset(pointer ptr = pointer()) {
- if (ptr == ptr_) {
- return;
- }
-
- pointer old_ptr = ptr_;
- ptr_ = ptr;
- std::free(reinterpret_cast<void*>(old_ptr));
+// Based on std::unique_ptr, but uses free() to release malloc'ed memory.
+struct FreeDeleter {
+ void operator()(void* ptr) const {
+ ::free(ptr);
}
+};
+template <typename T>
+using unique_cptr = std::unique_ptr<T, FreeDeleter>;
- inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); }
-
- inline explicit operator bool() const { return ptr_ != nullptr; }
-
- inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; }
-
- inline pointer operator->() const { return ptr_; }
+void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
- inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; }
+// Converts a UTF-8 string to a UTF-16 string.
+std::u16string Utf8ToUtf16(StringPiece utf8);
- inline bool operator!=(const unique_cptr& o) const { return ptr_ != o.ptr_; }
+// Converts a UTF-16 string to a UTF-8 string.
+std::string Utf16ToUtf8(StringPiece16 utf16);
- inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
+// Converts a UTF8 string into Modified UTF8
+std::string Utf8ToModifiedUtf8(std::string_view utf8);
- inline bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; }
+// Converts a Modified UTF8 string into a UTF8 string
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8);
- private:
- DISALLOW_COPY_AND_ASSIGN(unique_cptr);
+inline uint16_t HostToDevice16(uint16_t value) {
+ return htods(value);
+}
- pointer ptr_;
-};
+inline uint32_t HostToDevice32(uint32_t value) {
+ return htodl(value);
+}
-void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
+inline uint16_t DeviceToHost16(uint16_t value) {
+ return dtohs(value);
+}
-// Converts a UTF-8 string to a UTF-16 string.
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
+inline uint32_t DeviceToHost32(uint32_t value) {
+ return dtohl(value);
+}
-// Converts a UTF-16 string to a UTF-8 string.
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+inline bool IsFourByteAligned(const void* data) {
+ return ((uintptr_t)data & 0x3U) == 0;
+}
template <typename T>
inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
- return ((size_t)data.unsafe_ptr() & 0x3U) == 0;
+ return IsFourByteAligned(data.unsafe_ptr());
}
-inline bool IsFourByteAligned(const void* data) {
- return ((size_t)data & 0x3U) == 0;
-}
+// Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8,
+// the conversion to UTF-16 happens within ResStringPool.
+android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);
+
+// Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16,
+// the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method,
+// which maintains no state or cache. This means we must return an std::string copy.
+std::string GetString(const android::ResStringPool& pool, size_t idx);
+
+// Copies the entire BigBuffer into a single buffer.
+std::unique_ptr<uint8_t[]> Copy(const android::BigBuffer& buffer);
} // namespace util
} // namespace android
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e29125d..d40d24ede769 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@ FileType getFileType(const char* fileName);
/* get the file's modification date; returns -1 w/errno set on failure */
time_t getFileModDate(const char* fileName);
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
}; // namespace android
#endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 52854205207c..d3949e9cf69f 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
//
#include <androidfw/misc.h>
-#include <sys/stat.h>
+#include "android-base/logging.h"
+
+#ifdef __linux__
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif // __linux__
+
#include <cstring>
-#include <errno.h>
#include <cstdio>
-
-using namespace android;
+#include <errno.h>
+#include <sys/stat.h>
namespace android {
@@ -41,8 +46,7 @@ FileType getFileType(const char* fileName)
if (errno == ENOENT || errno == ENOTDIR)
return kFileTypeNonexistent;
else {
- fprintf(stderr, "getFileType got errno=%d on '%s'\n",
- errno, fileName);
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
return kFileTypeUnknown;
}
} else {
@@ -82,4 +86,32 @@ time_t getFileModDate(const char* fileName)
return sb.st_mtime;
}
+#ifndef __linux__
+// No need to implement these on the host, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+ return false;
+}
+bool isReadonlyFilesystem(int) {
+ return false;
+}
+#else // __linux__
+bool isReadonlyFilesystem(const char* path) {
+ struct statfs sfs;
+ if (::statfs(path, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+ struct statfs sfs;
+ if (::fstatfs(fd, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif // __linux__
+
}; // namespace android
diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp
new file mode 100644
index 000000000000..62e88c619e5c
--- /dev/null
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/ApkParsing.h"
+
+#include "android-base/test_utils.h"
+
+#include "TestHelpers.h"
+
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+TEST(ApkParsingTest, ValidArm64Path) {
+ const char* path = "lib/arm64-v8a/library.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, NotNull());
+ ASSERT_THAT(lastSlash, Eq(path + 13));
+}
+
+TEST(ApkParsingTest, ValidArm64PathButSuppressed) {
+ const char* path = "lib/arm64-v8a/library.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, true, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+
+TEST(ApkParsingTest, ValidArm32Path) {
+ const char* path = "lib/armeabi-v7a/library.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, NotNull());
+ ASSERT_THAT(lastSlash, Eq(path + 15));
+}
+
+TEST(ApkParsingTest, InvalidMustStartWithLib) {
+ const char* path = "lib/arm64-v8a/random.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+
+TEST(ApkParsingTest, InvalidMustEndInSo) {
+ const char* path = "lib/arm64-v8a/library.txt";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+
+TEST(ApkParsingTest, InvalidCharacter) {
+ const char* path = "lib/arm64-v8a/lib#.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+
+TEST(ApkParsingTest, InvalidSubdirectories) {
+ const char* path = "lib/arm64-v8a/anything/library.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+
+TEST(ApkParsingTest, InvalidFileAtRoot) {
+ const char* path = "lib/library.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
+} \ No newline at end of file
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index ddd8ab820cb1..1c89c61c8f78 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -120,8 +120,8 @@ static void BM_ApplyStyleFramework(benchmark::State& state) {
return;
}
- std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie,
- Asset::ACCESS_BUFFER);
+ std::unique_ptr<Asset> asset =
+ assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER);
if (asset == nullptr) {
state.SkipWithError("failed to load layout");
return;
diff --git a/libs/androidfw/tests/BigBuffer_test.cpp b/libs/androidfw/tests/BigBuffer_test.cpp
new file mode 100644
index 000000000000..382d21e20846
--- /dev/null
+++ b/libs/androidfw/tests/BigBuffer_test.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/BigBuffer.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::NotNull;
+
+namespace android {
+
+TEST(BigBufferTest, AllocateSingleBlock) {
+ BigBuffer buffer(4);
+
+ EXPECT_THAT(buffer.NextBlock<char>(2), NotNull());
+ EXPECT_EQ(2u, buffer.size());
+}
+
+TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
+ BigBuffer buffer(16);
+
+ char* b1 = buffer.NextBlock<char>(8);
+ EXPECT_THAT(b1, NotNull());
+
+ char* b2 = buffer.NextBlock<char>(4);
+ EXPECT_THAT(b2, NotNull());
+
+ EXPECT_EQ(b1 + 8, b2);
+}
+
+TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
+ BigBuffer buffer(16);
+
+ EXPECT_THAT(buffer.NextBlock<char>(32), NotNull());
+ EXPECT_EQ(32u, buffer.size());
+}
+
+TEST(BigBufferTest, AppendAndMoveBlock) {
+ BigBuffer buffer(16);
+
+ uint32_t* b1 = buffer.NextBlock<uint32_t>();
+ ASSERT_THAT(b1, NotNull());
+ *b1 = 33;
+
+ {
+ BigBuffer buffer2(16);
+ b1 = buffer2.NextBlock<uint32_t>();
+ ASSERT_THAT(b1, NotNull());
+ *b1 = 44;
+
+ buffer.AppendBuffer(std::move(buffer2));
+ EXPECT_EQ(0u, buffer2.size()); // NOLINT
+ EXPECT_EQ(buffer2.begin(), buffer2.end());
+ }
+
+ EXPECT_EQ(2 * sizeof(uint32_t), buffer.size());
+
+ auto b = buffer.begin();
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
+
+ ASSERT_NE(b, buffer.end());
+ ASSERT_EQ(sizeof(uint32_t), b->size);
+ ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
+ ++b;
+
+ ASSERT_EQ(b, buffer.end());
+}
+
+TEST(BigBufferTest, PadAndAlignProperly) {
+ BigBuffer buffer(16);
+
+ ASSERT_THAT(buffer.NextBlock<char>(2), NotNull());
+ ASSERT_EQ(2u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(4u, buffer.size());
+ buffer.Pad(2);
+ ASSERT_EQ(6u, buffer.size());
+ buffer.Align4();
+ ASSERT_EQ(8u, buffer.size());
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7dc0f7..9c36cfb212c5 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@ TEST(ByteBucketArrayTest, TestSparseInsertion) {
}
}
+TEST(ByteBucketArrayTest, TestForEach) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(10, 2));
+ ASSERT_TRUE(bba.set(26, 3));
+ ASSERT_TRUE(bba.set(129, 4));
+ ASSERT_TRUE(bba.set(234, 5));
+
+ int count = 0;
+ bba.forEachItem([&count](auto i, auto val) {
+ ++count;
+ switch (i) {
+ case 0:
+ EXPECT_EQ(1, val);
+ break;
+ case 10:
+ EXPECT_EQ(2, val);
+ break;
+ case 26:
+ EXPECT_EQ(3, val);
+ break;
+ case 129:
+ EXPECT_EQ(4, val);
+ break;
+ case 234:
+ EXPECT_EQ(5, val);
+ break;
+ default:
+ EXPECT_EQ(0, val);
+ break;
+ }
+ });
+ ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(255, 2));
+ {
+ bba.trimBuckets([](auto val) { return val < 2; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(1 * 16, count);
+ }
+ {
+ bba.trimBuckets([](auto val) { return val < 3; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(0, count);
+ }
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index ce7f8054e2ca..f5c01e5d9b68 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -25,8 +25,8 @@
namespace android {
-static ::testing::AssertionResult TestParse(
- const StringPiece& input, ConfigDescription* config = nullptr) {
+static ::testing::AssertionResult TestParse(StringPiece input,
+ ConfigDescription* config = nullptr) {
if (ConfigDescription::Parse(input, config)) {
return ::testing::AssertionSuccess() << input << " was successfully parsed";
}
@@ -138,7 +138,7 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) {
EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
}
-static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) {
ConfigDescription config;
CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
return config;
@@ -154,4 +154,22 @@ TEST(ConfigDescriptionTest, RangeQualifiersDoNotConflict) {
EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200")));
}
+TEST(ConfigDescriptionTest, TestGrammaticalGenderQualifier) {
+ ConfigDescription config;
+ EXPECT_TRUE(TestParse("feminine", &config));
+ EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_FEMININE, config.grammaticalInflection);
+ EXPECT_EQ(SDK_U, config.sdkVersion);
+ EXPECT_EQ(std::string("feminine-v34"), config.toString().string());
+
+ EXPECT_TRUE(TestParse("masculine", &config));
+ EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE, config.grammaticalInflection);
+ EXPECT_EQ(SDK_U, config.sdkVersion);
+ EXPECT_EQ(std::string("masculine-v34"), config.toString().string());
+
+ EXPECT_TRUE(TestParse("neuter", &config));
+ EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_NEUTER, config.grammaticalInflection);
+ EXPECT_EQ(SDK_U, config.sdkVersion);
+ EXPECT_EQ(std::string("neuter-v34"), config.toString().string());
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index 698c36f09301..5477621ce9fd 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -205,4 +205,18 @@ TEST(ConfigTest, ScreenIsHdr) {
EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE);
}
+TEST(ConfigTest, GrammaticalGender) {
+ ResTable_config defaultConfig = {};
+ ResTable_config masculine = {};
+ masculine.grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_MASCULINE;
+
+ EXPECT_EQ(defaultConfig.diff(masculine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+
+ ResTable_config feminine = {};
+ feminine.grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_FEMININE;
+
+ EXPECT_EQ(defaultConfig.diff(feminine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+ EXPECT_EQ(masculine.diff(feminine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+}
+
} // namespace android.
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d214e2dfef7b..c90ec197b5ef 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -71,62 +71,6 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) {
ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
}
-TEST(LoadedArscTest, LoadSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- auto type = type_spec->type_entries[0];
- ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
-}
-
-TEST(LoadedArscTest, FindSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- // Ensure that AAPT2 sparsely encoded the v26 config as expected.
- auto type_entry = std::find_if(
- type_spec->type_entries.begin(), type_spec->type_entries.end(),
- [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
- ASSERT_NE(type_entry, type_spec->type_entries.end());
- ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
-
- // Test fetching a resource with only sparsely encoded configs by name.
- auto id = package->FindEntryByName(u"string", u"only_v26");
- ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
-}
-
TEST(LoadedArscTest, LoadSharedLibrary) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
@@ -404,4 +348,84 @@ TEST(LoadedArscTest, LoadCustomLoader) {
// sizeof(Res_value) might not be backwards compatible.
// TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+class LoadedArscParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ auto type = type_spec->type_entries[0];
+ ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
+}
+
+TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1;
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ // Type Entry with default orientation is not sparse encoded because the ratio of
+ // populated entries to total entries is above threshold.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_default = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_default, type_spec->type_entries.end());
+ ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Type Entry with land orientation is sparse encoded as expected.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_land = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [](const TypeSpec::TypeEntry& x) { return x.config.orientation ==
+ ResTable_config::ORIENTATION_LAND &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_land, type_spec->type_entries.end());
+ ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Test fetching a resource with only sparsely encoded configs by name.
+ auto id = package->FindEntryByName(u"string", u"only_land");
+ ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesLoadedArscTests,
+ LoadedArscParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp
index 8c49350796ec..097e6b0bba65 100644
--- a/libs/androidfw/tests/PosixUtils_test.cpp
+++ b/libs/androidfw/tests/PosixUtils_test.cpp
@@ -28,27 +28,27 @@ namespace util {
TEST(PosixUtilsTest, AbsolutePathToBinary) {
const auto result = ExecuteBinary({"/bin/date", "--help"});
- ASSERT_THAT(result, NotNull());
- ASSERT_EQ(result->status, 0);
- ASSERT_GE(result->stdout_str.find("usage: date "), 0);
+ ASSERT_TRUE((bool)result);
+ ASSERT_EQ(result.status, 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_GE(result->stdout_str.find("usage: date "), 0);
+ ASSERT_TRUE((bool)result);
+ ASSERT_EQ(result.status, 0);
+ ASSERT_GE(result.stdout_str.find("usage: date "), 0);
}
TEST(PosixUtilsTest, BadParameters) {
const auto result = ExecuteBinary({"/bin/date", "--this-parameter-is-not-supported"});
- ASSERT_THAT(result, NotNull());
- ASSERT_NE(result->status, 0);
+ ASSERT_TRUE((bool)result);
+ ASSERT_GT(result.status, 0);
}
TEST(PosixUtilsTest, NoSuchBinary) {
const auto result = ExecuteBinary({"/this/binary/does/not/exist"});
- ASSERT_THAT(result, IsNull());
+ ASSERT_FALSE((bool)result);
}
} // android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 9aeb00c47e63..fbf70981f2de 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -15,6 +15,7 @@
*/
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include <codecvt>
#include <locale>
@@ -41,34 +42,6 @@ TEST(ResTableTest, ShouldLoadSuccessfully) {
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
}
-TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- ResTable table;
- ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
-
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- table.setParameters(&config);
-
- String16 name(u"com.android.sparse:integer/foo_9");
- uint32_t flags;
- uint32_t resid =
- table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
- ASSERT_NE(0u, resid);
-
- Res_value val;
- ResTable_config selected_config;
- ASSERT_GE(
- table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
- 0);
- EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- EXPECT_EQ(900u, val.data);
-}
-
TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
@@ -476,4 +449,43 @@ TEST(ResTableTest, TruncatedEncodeLength) {
ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value());
}
+class ResTableParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ table.setParameters(&config);
+
+ String16 name(u"com.android.sparse:integer/foo_9");
+ uint32_t flags;
+ uint32_t resid =
+ table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+ ASSERT_NE(0u, resid);
+
+ Res_value val;
+ ResTable_config selected_config;
+ ASSERT_GE(
+ table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+ 0);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ EXPECT_EQ(900u, val.data);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesResTableTests,
+ ResTableParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/ResourceTimer_test.cpp b/libs/androidfw/tests/ResourceTimer_test.cpp
new file mode 100644
index 000000000000..4a1e9735de7a
--- /dev/null
+++ b/libs/androidfw/tests/ResourceTimer_test.cpp
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <androidfw/Util.h>
+
+#include "TestHelpers.h"
+
+#include <androidfw/ResourceTimer.h>
+
+namespace android {
+
+namespace {
+
+// Create a reading in us. This is a convenience function to avoid multiplying by 1000
+// everywhere.
+unsigned int US(int us) {
+ return us * 1000;
+}
+
+}
+
+TEST(ResourceTimerTest, TimerBasic) {
+ ResourceTimer::Timer timer;
+ ASSERT_THAT(timer.count, 0);
+ ASSERT_THAT(timer.total, 0);
+
+ for (int i = 1; i <= 100; i++) {
+ timer.record(US(i));
+ }
+ ASSERT_THAT(timer.count, 100);
+ ASSERT_THAT(timer.total, US((101 * 100)/2));
+ ASSERT_THAT(timer.mintime, US(1));
+ ASSERT_THAT(timer.maxtime, US(100));
+ ASSERT_THAT(timer.pvalues.p50.floor, 0);
+ ASSERT_THAT(timer.pvalues.p50.nominal, 0);
+ ASSERT_THAT(timer.largest[0], US(100));
+ ASSERT_THAT(timer.largest[1], US(99));
+ ASSERT_THAT(timer.largest[2], US(98));
+ ASSERT_THAT(timer.largest[3], US(97));
+ ASSERT_THAT(timer.largest[4], US(96));
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(49));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(50));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(89));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(90));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(94));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(95));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(98));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(99));
+
+ // Test reset functionality. All values should be zero after the reset. Computing pvalues
+ // after the result should also yield zeros.
+ timer.reset();
+ ASSERT_THAT(timer.count, 0);
+ ASSERT_THAT(timer.total, 0);
+ ASSERT_THAT(timer.mintime, US(0));
+ ASSERT_THAT(timer.maxtime, US(0));
+ ASSERT_THAT(timer.pvalues.p50.floor, US(0));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(0));
+ ASSERT_THAT(timer.largest[0], US(0));
+ ASSERT_THAT(timer.largest[1], US(0));
+ ASSERT_THAT(timer.largest[2], US(0));
+ ASSERT_THAT(timer.largest[3], US(0));
+ ASSERT_THAT(timer.largest[4], US(0));
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(0));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(0));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(0));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(0));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(0));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(0));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(0));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(0));
+
+ // Test again, adding elements in reverse.
+ for (int i = 100; i >= 1; i--) {
+ timer.record(US(i));
+ }
+ ASSERT_THAT(timer.count, 100);
+ ASSERT_THAT(timer.total, US((101 * 100)/2));
+ ASSERT_THAT(timer.mintime, US(1));
+ ASSERT_THAT(timer.maxtime, US(100));
+ ASSERT_THAT(timer.pvalues.p50.floor, 0);
+ ASSERT_THAT(timer.pvalues.p50.nominal, 0);
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(49));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(50));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(89));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(90));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(94));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(95));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(98));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(99));
+ ASSERT_THAT(timer.largest[0], US(100));
+ ASSERT_THAT(timer.largest[1], US(99));
+ ASSERT_THAT(timer.largest[2], US(98));
+ ASSERT_THAT(timer.largest[3], US(97));
+ ASSERT_THAT(timer.largest[4], US(96));
+}
+
+TEST(ResourceTimerTest, TimerLimit) {
+ ResourceTimer::Timer timer;
+
+ // Event truncation means that a time of 1050us will be stored in the 1000us
+ // bucket. Since there is a single event, all p-values lie in the same range.
+ timer.record(US(1050));
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(900));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(1000));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(900));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(1000));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(900));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(1000));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(900));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(1000));
+}
+
+TEST(ResourceTimerTest, TimerCopy) {
+ ResourceTimer::Timer source;
+ for (int i = 1; i <= 100; i++) {
+ source.record(US(i));
+ }
+ ResourceTimer::Timer timer;
+ ResourceTimer::Timer::copy(timer, source, true);
+ ASSERT_THAT(source.count, 0);
+ ASSERT_THAT(source.total, 0);
+ // compute() is not normally be called on a reset timer, but it should work and it should return
+ // all zeros.
+ source.compute();
+ ASSERT_THAT(source.pvalues.p50.floor, US(0));
+ ASSERT_THAT(source.pvalues.p50.nominal, US(0));
+ ASSERT_THAT(source.pvalues.p90.floor, US(0));
+ ASSERT_THAT(source.pvalues.p90.nominal, US(0));
+ ASSERT_THAT(source.pvalues.p95.floor, US(0));
+ ASSERT_THAT(source.pvalues.p95.nominal, US(0));
+ ASSERT_THAT(source.pvalues.p99.floor, US(0));
+ ASSERT_THAT(source.pvalues.p99.nominal, US(0));
+ ASSERT_THAT(source.largest[0], US(0));
+ ASSERT_THAT(source.largest[1], US(0));
+ ASSERT_THAT(source.largest[2], US(0));
+ ASSERT_THAT(source.largest[3], US(0));
+ ASSERT_THAT(source.largest[4], US(0));
+
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(49));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(50));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(89));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(90));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(94));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(95));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(98));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(99));
+ ASSERT_THAT(timer.largest[0], US(100));
+ ASSERT_THAT(timer.largest[1], US(99));
+ ASSERT_THAT(timer.largest[2], US(98));
+ ASSERT_THAT(timer.largest[3], US(97));
+ ASSERT_THAT(timer.largest[4], US(96));
+
+ // Call compute a second time. The values must be the same.
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(49));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(50));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(89));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(90));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(94));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(95));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(98));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(99));
+ ASSERT_THAT(timer.largest[0], US(100));
+ ASSERT_THAT(timer.largest[1], US(99));
+ ASSERT_THAT(timer.largest[2], US(98));
+ ASSERT_THAT(timer.largest[3], US(97));
+ ASSERT_THAT(timer.largest[4], US(96));
+
+ // Modify the source. If timer and source share histogram arrays, this will introduce an
+ // error.
+ for (int i = 1; i <= 100; i++) {
+ source.record(US(i));
+ }
+ // Call compute a third time. The values must be the same.
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(49));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(50));
+ ASSERT_THAT(timer.pvalues.p90.floor, US(89));
+ ASSERT_THAT(timer.pvalues.p90.nominal, US(90));
+ ASSERT_THAT(timer.pvalues.p95.floor, US(94));
+ ASSERT_THAT(timer.pvalues.p95.nominal, US(95));
+ ASSERT_THAT(timer.pvalues.p99.floor, US(98));
+ ASSERT_THAT(timer.pvalues.p99.nominal, US(99));
+ ASSERT_THAT(timer.largest[0], US(100));
+ ASSERT_THAT(timer.largest[1], US(99));
+ ASSERT_THAT(timer.largest[2], US(98));
+ ASSERT_THAT(timer.largest[3], US(97));
+ ASSERT_THAT(timer.largest[4], US(96));
+}
+
+// Verify that if too many oversize entries are reported, the percentile values cannot be computed
+// and are set to zero.
+TEST(ResourceTimerTest, TimerOversize) {
+ static const int oversize = US(2 * 1000 * 1000);
+
+ ResourceTimer::Timer timer;
+ for (int i = 1; i <= 100; i++) {
+ timer.record(US(i));
+ }
+
+ // Insert enough oversize values to invalidate the p90, p95, and p99 percentiles. The p50 is
+ // still computable.
+ for (int i = 1; i <= 50; i++) {
+ timer.record(oversize);
+ }
+ ASSERT_THAT(timer.largest[0], oversize);
+ ASSERT_THAT(timer.largest[1], oversize);
+ ASSERT_THAT(timer.largest[2], oversize);
+ ASSERT_THAT(timer.largest[3], oversize);
+ ASSERT_THAT(timer.largest[4], oversize);
+ timer.compute();
+ ASSERT_THAT(timer.pvalues.p50.floor, US(74));
+ ASSERT_THAT(timer.pvalues.p50.nominal, US(75));
+ ASSERT_THAT(timer.pvalues.p90.floor, 0);
+ ASSERT_THAT(timer.pvalues.p90.nominal, 0);
+ ASSERT_THAT(timer.pvalues.p95.floor, 0);
+ ASSERT_THAT(timer.pvalues.p95.nominal, 0);
+ ASSERT_THAT(timer.pvalues.p99.floor, 0);
+ ASSERT_THAT(timer.pvalues.p99.nominal, 0);
+}
+
+
+} // namespace android
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index c9b4ad8af278..fffeeb802873 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -16,6 +16,7 @@
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include "BenchmarkHelpers.h"
#include "data/sparse/R.h"
@@ -24,40 +25,74 @@ namespace sparse = com::android::sparse;
namespace android {
+static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths,
+ uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)(
+ const std::vector<std::string>&, const ResTable_config*,
+ uint32_t, benchmark::State&)){
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ GetResourceBenchmarkFunc(paths, &config, resid, state);
+}
+
static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
+static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999);
+
} // namespace android
diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
index 316a5c1bf40e..822e527253df 100644
--- a/libs/androidfw/tests/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -60,36 +60,4 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
EXPECT_TRUE(StringPiece(car) > banana);
}
-TEST(StringPieceTest, ContainsOtherStringPiece) {
- StringPiece text("I am a leaf on the wind.");
- StringPiece start_needle("I am");
- StringPiece end_needle("wind.");
- StringPiece middle_needle("leaf");
- StringPiece empty_needle("");
- StringPiece missing_needle("soar");
- StringPiece long_needle("This string is longer than the text.");
-
- EXPECT_TRUE(text.contains(start_needle));
- EXPECT_TRUE(text.contains(end_needle));
- EXPECT_TRUE(text.contains(middle_needle));
- EXPECT_TRUE(text.contains(empty_needle));
- EXPECT_FALSE(text.contains(missing_needle));
- EXPECT_FALSE(text.contains(long_needle));
-
- StringPiece16 text16(u"I am a leaf on the wind.");
- StringPiece16 start_needle16(u"I am");
- StringPiece16 end_needle16(u"wind.");
- StringPiece16 middle_needle16(u"leaf");
- StringPiece16 empty_needle16(u"");
- StringPiece16 missing_needle16(u"soar");
- StringPiece16 long_needle16(u"This string is longer than the text.");
-
- EXPECT_TRUE(text16.contains(start_needle16));
- EXPECT_TRUE(text16.contains(end_needle16));
- EXPECT_TRUE(text16.contains(middle_needle16));
- EXPECT_TRUE(text16.contains(empty_needle16));
- EXPECT_FALSE(text16.contains(missing_needle16));
- EXPECT_FALSE(text16.contains(long_needle16));
-}
-
} // namespace android
diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp
new file mode 100644
index 000000000000..0e0acae165d9
--- /dev/null
+++ b/libs/androidfw/tests/StringPool_test.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/StringPool.h"
+
+#include <string>
+
+#include "androidfw/IDiagnostics.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::android::StringPiece;
+using ::android::StringPiece16;
+using ::testing::Eq;
+using ::testing::Ne;
+using ::testing::NotNull;
+using ::testing::Pointee;
+
+namespace android {
+
+TEST(StringPoolTest, InsertOneString) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.MakeRef("wut");
+ EXPECT_THAT(*ref, Eq("wut"));
+}
+
+TEST(StringPoolTest, InsertTwoUniqueStrings) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("wut");
+ StringPool::Ref ref_b = pool.MakeRef("hey");
+
+ EXPECT_THAT(*ref_a, Eq("wut"));
+ EXPECT_THAT(*ref_b, Eq("hey"));
+}
+
+TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("wut");
+ StringPool::Ref ref_b = pool.MakeRef("wut");
+
+ EXPECT_THAT(*ref_a, Eq("wut"));
+ EXPECT_THAT(*ref_b, Eq("wut"));
+ EXPECT_THAT(pool.size(), Eq(1u));
+}
+
+TEST(StringPoolTest, DoNotDedupeSameStringDifferentPriority) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("wut", StringPool::Context(0x81010001));
+ StringPool::Ref ref_b = pool.MakeRef("wut", StringPool::Context(0x81010002));
+
+ EXPECT_THAT(*ref_a, Eq("wut"));
+ EXPECT_THAT(*ref_b, Eq("wut"));
+ EXPECT_THAT(pool.size(), Eq(2u));
+}
+
+TEST(StringPoolTest, MaintainInsertionOrderIndex) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
+
+ EXPECT_THAT(ref_a.index(), Eq(0u));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
+ EXPECT_THAT(ref_c.index(), Eq(2u));
+}
+
+TEST(StringPoolTest, PruneStringsWithNoReferences) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("foo");
+
+ {
+ StringPool::Ref ref_b = pool.MakeRef("wut");
+ EXPECT_THAT(*ref_b, Eq("wut"));
+ EXPECT_THAT(pool.size(), Eq(2u));
+ pool.Prune();
+ EXPECT_THAT(pool.size(), Eq(2u));
+ }
+ EXPECT_THAT(pool.size(), Eq(2u));
+
+ {
+ StringPool::Ref ref_c = pool.MakeRef("bar");
+ EXPECT_THAT(pool.size(), Eq(3u));
+
+ pool.Prune();
+ EXPECT_THAT(pool.size(), Eq(2u));
+ }
+ EXPECT_THAT(pool.size(), Eq(2u));
+
+ pool.Prune();
+ EXPECT_THAT(pool.size(), Eq(1u));
+}
+
+TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
+
+ EXPECT_THAT(*ref_a, Eq("z"));
+ EXPECT_THAT(ref_a.index(), Eq(0u));
+
+ EXPECT_THAT(*ref_b, Eq("a"));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
+
+ EXPECT_THAT(*ref_c, Eq("m"));
+ EXPECT_THAT(ref_c.index(), Eq(2u));
+
+ pool.Sort();
+
+ EXPECT_THAT(*ref_a, Eq("z"));
+ EXPECT_THAT(ref_a.index(), Eq(2u));
+
+ EXPECT_THAT(*ref_b, Eq("a"));
+ EXPECT_THAT(ref_b.index(), Eq(0u));
+
+ EXPECT_THAT(*ref_c, Eq("m"));
+ EXPECT_THAT(ref_c.index(), Eq(1u));
+}
+
+TEST(StringPoolTest, SortAndStillDedupe) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
+
+ pool.Sort();
+
+ StringPool::Ref ref_d = pool.MakeRef("z");
+ StringPool::Ref ref_e = pool.MakeRef("a");
+ StringPool::Ref ref_f = pool.MakeRef("m");
+
+ EXPECT_THAT(ref_d.index(), Eq(ref_a.index()));
+ EXPECT_THAT(ref_e.index(), Eq(ref_b.index()));
+ EXPECT_THAT(ref_f.index(), Eq(ref_c.index()));
+}
+
+TEST(StringPoolTest, AddStyles) {
+ StringPool pool;
+
+ StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}});
+ EXPECT_THAT(ref.index(), Eq(0u));
+ EXPECT_THAT(ref->value, Eq("android"));
+ ASSERT_THAT(ref->spans.size(), Eq(1u));
+
+ const StringPool::Span& span = ref->spans.front();
+ EXPECT_THAT(*span.name, Eq("b"));
+ EXPECT_THAT(span.first_char, Eq(2u));
+ EXPECT_THAT(span.last_char, Eq(6u));
+}
+
+TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
+ StringPool pool;
+
+ StringPool::Ref ref = pool.MakeRef("android");
+
+ StyleString str{{"android"}, {}};
+ StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}, {}});
+
+ EXPECT_THAT(ref.index(), Ne(style_ref.index()));
+}
+
+TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) {
+ StringPool pool;
+
+ StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}, {}});
+ StringPool::Ref ref_b = pool.MakeRef("alpha");
+ StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}, {}});
+
+ EXPECT_THAT(ref_b.index(), Ne(ref_c.index()));
+
+ pool.Sort();
+
+ EXPECT_THAT(ref_c.index(), Eq(0u));
+ EXPECT_THAT(ref_a.index(), Eq(1u));
+ EXPECT_THAT(ref_b.index(), Eq(2u));
+}
+
+TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ using namespace android; // For NO_ERROR on Windows.
+ NoOpDiagnostics diag;
+
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool, &diag);
+
+ std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer);
+ ResStringPool test;
+ ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR));
+}
+
+TEST(StringPoolTest, FlattenOddCharactersUtf16) {
+ using namespace android; // For NO_ERROR on Windows.
+ NoOpDiagnostics diag;
+
+ StringPool pool;
+ pool.MakeRef("\u093f");
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf16(&buffer, pool, &diag);
+
+ std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer);
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ auto str = test.stringAt(0);
+ ASSERT_TRUE(str.has_value());
+ EXPECT_THAT(str->size(), Eq(1u));
+ EXPECT_THAT(str->data(), Pointee(Eq(u'\u093f')));
+ EXPECT_THAT(str->data()[1], Eq(0u));
+}
+
+constexpr const char* sLongString =
+ "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑"
+ "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限"
+ "します。メール、SMSや、同期を使 "
+ "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ"
+ "ーバーは端末の充電中は自動的にOFFになります。";
+
+TEST(StringPoolTest, Flatten) {
+ using namespace android; // For NO_ERROR on Windows.
+ NoOpDiagnostics diag;
+
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("hello");
+ StringPool::Ref ref_b = pool.MakeRef("goodbye");
+ StringPool::Ref ref_c = pool.MakeRef(sLongString);
+ StringPool::Ref ref_d = pool.MakeRef("");
+ StringPool::StyleRef ref_e =
+ pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
+
+ // Styles are always first.
+ EXPECT_THAT(ref_e.index(), Eq(0u));
+
+ EXPECT_THAT(ref_a.index(), Eq(1u));
+ EXPECT_THAT(ref_b.index(), Eq(2u));
+ EXPECT_THAT(ref_c.index(), Eq(3u));
+ EXPECT_THAT(ref_d.index(), Eq(4u));
+
+ BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
+ StringPool::FlattenUtf8(&buffers[0], pool, &diag);
+ StringPool::FlattenUtf16(&buffers[1], pool, &diag);
+
+ // Test both UTF-8 and UTF-16 buffers.
+ for (const BigBuffer& buffer : buffers) {
+ std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer);
+
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+
+ EXPECT_THAT(android::util::GetString(test, 1), Eq("hello"));
+ EXPECT_THAT(android::util::GetString16(test, 1), Eq(u"hello"));
+
+ EXPECT_THAT(android::util::GetString(test, 2), Eq("goodbye"));
+ EXPECT_THAT(android::util::GetString16(test, 2), Eq(u"goodbye"));
+
+ EXPECT_THAT(android::util::GetString(test, 3), Eq(sLongString));
+ EXPECT_THAT(android::util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString)));
+
+ EXPECT_TRUE(test.stringAt(4).has_value() || test.string8At(4).has_value());
+
+ EXPECT_THAT(android::util::GetString(test, 0), Eq("style"));
+ EXPECT_THAT(android::util::GetString16(test, 0), Eq(u"style"));
+
+ auto span_result = test.styleAt(0);
+ ASSERT_TRUE(span_result.has_value());
+
+ const ResStringPool_span* span = span_result->unsafe_ptr();
+ EXPECT_THAT(android::util::GetString(test, span->name.index), Eq("b"));
+ EXPECT_THAT(android::util::GetString16(test, span->name.index), Eq(u"b"));
+ EXPECT_THAT(span->firstChar, Eq(0u));
+ EXPECT_THAT(span->lastChar, Eq(1u));
+ span++;
+
+ ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END));
+ EXPECT_THAT(android::util::GetString(test, span->name.index), Eq("i"));
+ EXPECT_THAT(android::util::GetString16(test, span->name.index), Eq(u"i"));
+ EXPECT_THAT(span->firstChar, Eq(2u));
+ EXPECT_THAT(span->lastChar, Eq(3u));
+ span++;
+
+ EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END));
+ }
+}
+
+TEST(StringPoolTest, ModifiedUTF8) {
+ using namespace android; // For NO_ERROR on Windows.
+ NoOpDiagnostics diag;
+ StringPool pool;
+ StringPool::Ref ref_a = pool.MakeRef("\xF0\x90\x90\x80"); // 𐐀 (U+10400)
+ StringPool::Ref ref_b = pool.MakeRef("foo \xF0\x90\x90\xB7 bar"); // 𐐷 (U+10437)
+ StringPool::Ref ref_c = pool.MakeRef("\xF0\x90\x90\x80\xF0\x90\x90\xB7");
+
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool, &diag);
+ std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer);
+
+ // Check that the codepoints are encoded using two three-byte surrogate pairs
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ auto str = test.string8At(0);
+ ASSERT_TRUE(str.has_value());
+ EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80"));
+
+ str = test.string8At(1);
+ ASSERT_TRUE(str.has_value());
+ EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+
+ str = test.string8At(2);
+ ASSERT_TRUE(str.has_value());
+ EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+
+ // Check that retrieving the strings returns the original UTF-8 character bytes
+ EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80"));
+ EXPECT_THAT(android::util::GetString(test, 1), Eq("foo \xF0\x90\x90\xB7 bar"));
+ EXPECT_THAT(android::util::GetString(test, 2), Eq("\xF0\x90\x90\x80\xF0\x90\x90\xB7"));
+}
+
+TEST(StringPoolTest, MaxEncodingLength) {
+ NoOpDiagnostics diag;
+ using namespace android; // For NO_ERROR on Windows.
+ ResStringPool test;
+
+ StringPool pool;
+ pool.MakeRef("aaaaaaaaaa");
+ BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
+
+ // Make sure a UTF-8 string under the maximum length does not produce an error
+ EXPECT_THAT(StringPool::FlattenUtf8(&buffers[0], pool, &diag), Eq(true));
+ std::unique_ptr<uint8_t[]> data = android::util::Copy(buffers[0]);
+ test.setTo(data.get(), buffers[0].size());
+ EXPECT_THAT(android::util::GetString(test, 0), Eq("aaaaaaaaaa"));
+
+ // Make sure a UTF-16 string under the maximum length does not produce an error
+ EXPECT_THAT(StringPool::FlattenUtf16(&buffers[1], pool, &diag), Eq(true));
+ data = android::util::Copy(buffers[1]);
+ test.setTo(data.get(), buffers[1].size());
+ EXPECT_THAT(android::util::GetString16(test, 0), Eq(u"aaaaaaaaaa"));
+
+ StringPool pool2;
+ std::string longStr(50000, 'a');
+ pool2.MakeRef("this fits1");
+ pool2.MakeRef(longStr);
+ pool2.MakeRef("this fits2");
+ BigBuffer buffers2[2] = {BigBuffer(1024), BigBuffer(1024)};
+
+ // Make sure a string that exceeds the maximum length of UTF-8 produces an
+ // error and writes a shorter error string instead
+ EXPECT_THAT(StringPool::FlattenUtf8(&buffers2[0], pool2, &diag), Eq(false));
+ data = android::util::Copy(buffers2[0]);
+ test.setTo(data.get(), buffers2[0].size());
+ EXPECT_THAT(android::util::GetString(test, 0), "this fits1");
+ EXPECT_THAT(android::util::GetString(test, 1), "STRING_TOO_LARGE");
+ EXPECT_THAT(android::util::GetString(test, 2), "this fits2");
+
+ // Make sure a string that a string that exceeds the maximum length of UTF-8
+ // but not UTF-16 does not error for UTF-16
+ StringPool pool3;
+ std::u16string longStr16(50000, 'a');
+ pool3.MakeRef(longStr);
+ EXPECT_THAT(StringPool::FlattenUtf16(&buffers2[1], pool3, &diag), Eq(true));
+ data = android::util::Copy(buffers2[1]);
+ test.setTo(data.get(), buffers2[1].size());
+ EXPECT_THAT(android::util::GetString16(test, 0), Eq(longStr16));
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5d0f11..ed30904ec179 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <algorithm>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
#include <utils/String8.h>
@@ -22,88 +23,123 @@
namespace android {
-void* createTypeData() {
- ResTable_type t;
- memset(&t, 0, sizeof(t));
+// create a ResTable_type in memory with a vector of Res_value*
+static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
+ bool compact_entry = false,
+ bool short_offsets = false)
+{
+ ResTable_type t{};
t.header.type = RES_TABLE_TYPE_TYPE;
t.header.headerSize = sizeof(t);
+ t.header.size = sizeof(t);
t.id = 1;
- t.entryCount = 3;
-
- uint32_t offsets[3];
- t.entriesStart = t.header.headerSize + sizeof(offsets);
- t.header.size = t.entriesStart;
-
- offsets[0] = 0;
- ResTable_entry e1;
- memset(&e1, 0, sizeof(e1));
- e1.size = sizeof(e1);
- e1.key.index = 0;
- t.header.size += sizeof(e1);
-
- Res_value v1;
- memset(&v1, 0, sizeof(v1));
- t.header.size += sizeof(v1);
-
- offsets[1] = ResTable_type::NO_ENTRY;
-
- offsets[2] = sizeof(e1) + sizeof(v1);
- ResTable_entry e2;
- memset(&e2, 0, sizeof(e2));
- e2.size = sizeof(e2);
- e2.key.index = 1;
- t.header.size += sizeof(e2);
-
- Res_value v2;
- memset(&v2, 0, sizeof(v2));
- t.header.size += sizeof(v2);
-
- uint8_t* data = (uint8_t*)malloc(t.header.size);
- uint8_t* p = data;
- memcpy(p, &t, sizeof(t));
- p += sizeof(t);
- memcpy(p, offsets, sizeof(offsets));
- p += sizeof(offsets);
- memcpy(p, &e1, sizeof(e1));
- p += sizeof(e1);
- memcpy(p, &v1, sizeof(v1));
- p += sizeof(v1);
- memcpy(p, &e2, sizeof(e2));
- p += sizeof(e2);
- memcpy(p, &v2, sizeof(v2));
- p += sizeof(v2);
- return data;
+ t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0;
+
+ t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t));
+ t.entriesStart = t.header.size;
+ t.entryCount = values.size();
+
+ size_t entry_size = compact_entry ? sizeof(ResTable_entry)
+ : sizeof(ResTable_entry) + sizeof(Res_value);
+ for (auto const v : values) {
+ t.header.size += v ? entry_size : 0;
+ }
+
+ uint8_t* data = (uint8_t *)malloc(t.header.size);
+ uint8_t* p_header = data;
+ uint8_t* p_offsets = data + t.header.headerSize;
+ uint8_t* p_entries = data + t.entriesStart;
+
+ memcpy(p_header, &t, sizeof(t));
+
+ size_t i = 0, entry_offset = 0;
+ uint32_t k = 0;
+ for (auto const& v : values) {
+ if (short_offsets) {
+ uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i;
+ *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu;
+ } else {
+ uint32_t *p = reinterpret_cast<uint32_t *>(p_offsets) + i;
+ *p = v ? entry_offset : ResTable_type::NO_ENTRY;
+ }
+
+ if (v) {
+ ResTable_entry entry{};
+ if (compact_entry) {
+ entry.compact.key = i;
+ entry.compact.flags = ResTable_entry::FLAG_COMPACT | (v->dataType << 8);
+ entry.compact.data = v->data;
+ memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+ entry_offset += sizeof(entry);
+ } else {
+ Res_value value{};
+ entry.full.size = sizeof(entry);
+ entry.full.key.index = i;
+ value = *v;
+ memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry);
+ memcpy(p_entries, &value, sizeof(value)); p_entries += sizeof(value);
+ entry_offset += sizeof(entry) + sizeof(value);
+ }
+ }
+ i++;
+ }
+ return reinterpret_cast<ResTable_type*>(data);
}
TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
- ResTable_type* data = (ResTable_type*) createTypeData();
+ std::vector<Res_value *> values;
- TypeVariant v(data);
+ Res_value *v1 = new Res_value{};
+ values.push_back(v1);
- TypeVariant::iterator iter = v.beginEntries();
- ASSERT_EQ(uint32_t(0), iter.index());
- ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key.index);
- ASSERT_NE(v.endEntries(), iter);
+ values.push_back(nullptr);
- iter++;
+ Res_value *v2 = new Res_value{};
+ values.push_back(v2);
- ASSERT_EQ(uint32_t(1), iter.index());
- ASSERT_TRUE(NULL == *iter);
- ASSERT_NE(v.endEntries(), iter);
+ Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678};
+ values.push_back(v3);
- iter++;
+ // test for combinations of compact_entry and short_offsets
+ for (size_t i = 0; i < 4; i++) {
+ bool compact_entry = i & 0x1, short_offsets = i & 0x2;
+ ResTable_type* data = createTypeTable(values, compact_entry, short_offsets);
+ TypeVariant v(data);
- ASSERT_EQ(uint32_t(2), iter.index());
- ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(1), iter->key.index);
- ASSERT_NE(v.endEntries(), iter);
+ TypeVariant::iterator iter = v.beginEntries();
+ ASSERT_EQ(uint32_t(0), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(0), iter->key());
+ ASSERT_NE(v.endEntries(), iter);
- iter++;
+ iter++;
- ASSERT_EQ(v.endEntries(), iter);
+ ASSERT_EQ(uint32_t(1), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
- free(data);
+ iter++;
+
+ ASSERT_EQ(uint32_t(2), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(2), iter->key());
+ ASSERT_NE(v.endEntries(), iter);
+
+ iter++;
+
+ ASSERT_EQ(uint32_t(3), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(iter->is_compact(), compact_entry);
+ ASSERT_EQ(uint32_t(3), iter->key());
+ ASSERT_EQ(uint32_t(0x12345678), iter->value().data);
+ ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType);
+
+ iter++;
+
+ ASSERT_EQ(v.endEntries(), iter);
+
+ free(data);
+ }
}
} // namespace android
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 88eadccb38cf..8e847e81aa31 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp
new file mode 100644
index 000000000000..b0da375c7971
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/Android.bp
@@ -0,0 +1,23 @@
+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_libs_androidfw_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+android_test_helper_app {
+ name: "FrameworkResourcesSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+ aaptflags: [
+ "--enable-sparse-encoding",
+ ],
+}
+
+android_test_helper_app {
+ name: "FrameworkResourcesNotSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+}
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
index 27911b62447a..9c23a7227631 100644
--- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -17,4 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sparse">
<application />
+ <uses-sdk android:minSdkVersion="32" />
</manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 2492dbf33f4a..a66e1af150c4 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -42,7 +42,7 @@ struct R {
struct string {
enum : uint32_t {
foo_999 = 0x7f0203e7,
- only_v26 = 0x7f0203e8
+ only_land = 0x7f0203e8
};
};
};
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index 4ea5468c7df9..114ecbb7d860 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -1,20 +1,20 @@
#!/bin/bash
OUTPUT_default=res/values/strings.xml
-OUTPUT_v26=res/values-v26/strings.xml
+OUTPUT_land=res/values-land/strings.xml
echo "<resources>" > $OUTPUT_default
-echo "<resources>" > $OUTPUT_v26
+echo "<resources>" > $OUTPUT_land
for i in {0..999}
do
echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
if [ "$(($i % 3))" -eq "0" ]
then
- echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+ echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land
fi
done
echo "</resources>" >> $OUTPUT_default
-echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
-echo "</resources>" >> $OUTPUT_v26
+echo " <string name=\"only_land\">only land</string>" >> $OUTPUT_land
+echo "</resources>" >> $OUTPUT_land
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index b08a621195c0..4d4d4a849033 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
index d116087ec3c0..66222c327416 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
@@ -333,5 +333,5 @@
<string name="foo_993">9930</string>
<string name="foo_996">9960</string>
<string name="foo_999">9990</string>
- <string name="only_v26">only v26</string>
+ <string name="only_land">only land</string>
</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
index b396ad24aa8c..b396ad24aa8c 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 9fd01fbf2941..0f2d75a62b96 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 979a660d497f..3b129720c727 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -73,7 +73,6 @@ cc_defaults {
target: {
android: {
include_dirs: [
- "external/skia/src/effects",
"external/skia/src/image",
"external/skia/src/utils",
"external/skia/src/gpu",
@@ -333,11 +332,14 @@ cc_defaults {
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
"jni/android_graphics_DisplayListCanvas.cpp",
+ "jni/android_graphics_Mesh.cpp",
"jni/android_graphics_RenderNode.cpp",
"jni/android_nio_utils.cpp",
"jni/android_util_PathParser.cpp",
"jni/Bitmap.cpp",
+ "jni/BufferUtils.cpp",
+ "jni/HardwareBufferHelpers.cpp",
"jni/BitmapFactory.cpp",
"jni/ByteBufferStreamAdaptor.cpp",
"jni/Camera.cpp",
@@ -346,9 +348,11 @@ cc_defaults {
"jni/CreateJavaOutputStreamAdaptor.cpp",
"jni/FontFamily.cpp",
"jni/FontUtils.cpp",
+ "jni/Gainmap.cpp",
"jni/Graphics.cpp",
"jni/ImageDecoder.cpp",
"jni/Interpolator.cpp",
+ "jni/MeshSpecification.cpp",
"jni/MaskFilter.cpp",
"jni/NinePatch.cpp",
"jni/NinePatchPeeker.cpp",
@@ -356,9 +360,11 @@ cc_defaults {
"jni/PaintFilter.cpp",
"jni/Path.cpp",
"jni/PathEffect.cpp",
+ "jni/PathIterator.cpp",
"jni/PathMeasure.cpp",
"jni/Picture.cpp",
"jni/Region.cpp",
+ "jni/ScopedParcel.cpp",
"jni/Shader.cpp",
"jni/RenderEffect.cpp",
"jni/Typeface.cpp",
@@ -369,27 +375,30 @@ cc_defaults {
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
"jni/text/TextShaper.cpp",
+ "jni/text/GraphemeBreak.cpp",
],
- header_libs: ["android_graphics_jni_headers"],
+ header_libs: [
+ "android_graphics_jni_headers",
+ "libnativewindow_headers",
+ ],
include_dirs: [
"external/skia/include/private",
"external/skia/src/codec",
"external/skia/src/core",
- "external/skia/src/effects",
- "external/skia/src/image",
- "external/skia/src/images",
],
shared_libs: [
"libbase",
"libcutils",
"libharfbuzz_ng",
+ "libimage_io",
+ "libjpeg",
+ "libjpegrecoverymap",
"liblog",
"libminikin",
"libz",
- "libjpeg",
],
static_libs: [
@@ -403,6 +412,7 @@ cc_defaults {
"jni/AnimatedImageDrawable.cpp",
"jni/android_graphics_TextureLayer.cpp",
"jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
@@ -498,6 +508,7 @@ cc_defaults {
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
"effects/StretchEffect.cpp",
+ "effects/GainmapRenderer.cpp",
"pipeline/skia/HolePunch.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
@@ -526,9 +537,12 @@ cc_defaults {
"AnimatorManager.cpp",
"CanvasTransform.cpp",
"DamageAccumulator.cpp",
+ "Gainmap.cpp",
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
+ "Mesh.cpp",
+ "MemoryPolicy.cpp",
"PathParser.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",
@@ -539,6 +553,7 @@ cc_defaults {
"RootRenderNode.cpp",
"SkiaCanvas.cpp",
"SkiaInterpolator.cpp",
+ "Tonemapper.cpp",
"VectorDrawable.cpp",
],
@@ -577,6 +592,7 @@ cc_defaults {
"renderthread/VulkanSurface.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderThread.cpp",
+ "renderthread/HintSessionWrapper.cpp",
"service/GraphicsStatsService.cpp",
"thread/CommonPool.cpp",
"utils/GLUtils.cpp",
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 911315f81a8a..75f61f5f7f9d 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="not-shardable" value="true" />
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp/nativetest" />
<option name="module-name" value="hwui_unit_tests" />
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index d0d24a8738f4..cd4fae86aa52 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -15,19 +15,21 @@
*/
#include "CanvasTransform.h"
-#include "Properties.h"
-#include "utils/Color.h"
+#include <SkAndroidFrameworkUtils.h>
+#include <SkBlendMode.h>
#include <SkColorFilter.h>
#include <SkGradientShader.h>
+#include <SkHighContrastFilter.h>
#include <SkPaint.h>
#include <SkShader.h>
+#include <log/log.h>
#include <algorithm>
#include <cmath>
-#include <log/log.h>
-#include <SkHighContrastFilter.h>
+#include "Properties.h"
+#include "utils/Color.h"
namespace android::uirenderer {
@@ -82,27 +84,21 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
paint.setColor(newColor);
if (paint.getShader()) {
- SkShader::GradientInfo info;
+ SkAndroidFrameworkUtils::LinearGradientInfo info;
std::array<SkColor, 10> _colorStorage;
std::array<SkScalar, _colorStorage.size()> _offsetStorage;
info.fColorCount = _colorStorage.size();
info.fColors = _colorStorage.data();
info.fColorOffsets = _offsetStorage.data();
- SkShader::GradientType type = paint.getShader()->asAGradient(&info);
-
- if (info.fColorCount <= 10) {
- switch (type) {
- case SkShader::kLinear_GradientType:
- for (int i = 0; i < info.fColorCount; i++) {
- info.fColors[i] = transformColor(transform, info.fColors[i]);
- }
- paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors,
- info.fColorOffsets, info.fColorCount,
- info.fTileMode, info.fGradientFlags, nullptr));
- break;
- default:break;
- }
+ if (SkAndroidFrameworkUtils::ShaderAsALinearGradient(paint.getShader(), &info) &&
+ info.fColorCount <= _colorStorage.size()) {
+ for (int i = 0; i < info.fColorCount; i++) {
+ info.fColors[i] = transformColor(transform, info.fColors[i]);
+ }
+ paint.setShader(SkGradientShader::MakeLinear(
+ info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
+ info.fTileMode, info.fGradientFlags, nullptr));
}
}
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index c46a2d369974..291f4cf7193b 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -45,4 +45,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale
SkColor transformColor(ColorTransform transform, SkColor color);
SkColor transformColorInverse(ColorTransform transform, SkColor color);
-} // namespace android::uirenderer; \ No newline at end of file
+} // namespace android::uirenderer
diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h
index 3df5c3c9caed..959cf742c8e4 100644
--- a/libs/hwui/ColorMode.h
+++ b/libs/hwui/ColorMode.h
@@ -25,9 +25,10 @@ enum class ColorMode {
// WideColorGamut selects the most optimal colorspace & format for the device's display
// Most commonly DisplayP3 + RGBA_8888 currently.
WideColorGamut = 1,
- // HDR Rec2020 + F16
+ // Extended range Display P3
Hdr = 2,
- // HDR Rec2020 + 1010102
+ // Extended range Display P3 10-bit
+ // for test purposes only, not shippable due to insuffient alpha
Hdr10 = 3,
// Alpha 8
A8 = 4,
diff --git a/libs/hwui/CopyRequest.h b/libs/hwui/CopyRequest.h
new file mode 100644
index 000000000000..5fbd5f900716
--- /dev/null
+++ b/libs/hwui/CopyRequest.h
@@ -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.
+ */
+
+#pragma once
+
+#include "Rect.h"
+#include "hwui/Bitmap.h"
+
+namespace android::uirenderer {
+
+// Keep in sync with PixelCopy.java codes
+enum class CopyResult {
+ Success = 0,
+ UnknownError = 1,
+ Timeout = 2,
+ SourceEmpty = 3,
+ SourceInvalid = 4,
+ DestinationInvalid = 5,
+};
+
+struct CopyRequest {
+ Rect srcRect;
+ CopyRequest(Rect srcRect) : srcRect(srcRect) {}
+ virtual ~CopyRequest() {}
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) = 0;
+ virtual void onCopyFinished(CopyResult result) = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 9a4c5505fa35..a7f8f6189a8e 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -16,6 +16,7 @@
#pragma once
+#include <SkBlendMode.h>
#include <SkColorFilter.h>
#include <SkImage.h>
#include <SkMatrix.h>
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 07594715a84c..32bc122fdc58 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -93,15 +93,25 @@ void DeviceInfo::setWideColorDataspace(ADataSpace dataspace) {
case ADATASPACE_SCRGB:
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
+ default:
+ ALOGW("Unknown dataspace %d", dataspace);
+ // Treat unknown dataspaces as sRGB, so fall through
+ [[fallthrough]];
case ADATASPACE_SRGB:
// when sRGB is returned, it means wide color gamut is not supported.
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
- default:
- LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
}
+void DeviceInfo::setSupportFp16ForHdr(bool supportFp16ForHdr) {
+ get()->mSupportFp16ForHdr = supportFp16ForHdr;
+}
+
+void DeviceInfo::setSupportMixedColorSpaces(bool supportMixedColorSpaces) {
+ get()->mSupportMixedColorSpaces = supportMixedColorSpaces;
+}
+
void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) {
mVsyncPeriod = vsyncPeriod;
}
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d5fee3f667a9..d4af0872e31e 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -16,7 +16,9 @@
#ifndef DEVICEINFO_H
#define DEVICEINFO_H
+#include <SkColorSpace.h>
#include <SkImageInfo.h>
+#include <SkRefCnt.h>
#include <android/data_space.h>
#include <mutex>
@@ -57,6 +59,12 @@ public:
}
static void setWideColorDataspace(ADataSpace dataspace);
+ static void setSupportFp16ForHdr(bool supportFp16ForHdr);
+ static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+
+ static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
+ static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
+
// this value is only valid after the GPU has been initialized and there is a valid graphics
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
@@ -86,6 +94,8 @@ private:
int mMaxTextureSize;
sk_sp<SkColorSpace> mWideColorSpace = SkColorSpace::MakeSRGB();
+ bool mSupportFp16ForHdr = false;
+ bool mSupportMixedColorSpaces = false;
SkColorType mWideColorType = SkColorType::kN32_SkColorType;
int mDisplaysSize = 0;
int mPhysicalDisplayIndex = -1;
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 4ec782f6fec0..a18ba1c633b9 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,3 +52,5 @@ X(DrawShadowRec)
X(DrawVectorDrawable)
X(DrawRippleDrawable)
X(DrawWebView)
+X(DrawSkMesh)
+X(DrawMesh) \ No newline at end of file
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 564ee4f53a54..b15b6cb9a9ec 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -104,6 +104,7 @@ public:
set(FrameInfoIndex::AnimationStart) = vsyncTime;
set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime;
set(FrameInfoIndex::DrawStart) = vsyncTime;
+ set(FrameInfoIndex::FrameStartTime) = vsyncTime;
set(FrameInfoIndex::FrameDeadline) = frameDeadline;
set(FrameInfoIndex::FrameInterval) = frameInterval;
return *this;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/hwui/Gainmap.cpp
index 6695c17ed514..30f401ef5f01 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
+++ b/libs/hwui/Gainmap.cpp
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
+/**
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,21 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include "Gainmap.h"
-package com.android.wm.shell.flicker.helpers
+namespace android::uirenderer {
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
- instrumentation,
- Components.LaunchBubbleActivity.LABEL,
- Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
-) {
-
- companion object {
- const val TEST_REPETITIONS = 1
- const val TIMEOUT_MS = 3_000L
+sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) {
+ auto gainmap = sp<Gainmap>::make();
+ gainmap->info = srcGainmap->info;
+ const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap();
+ sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap));
+ if (!skBitmap.get()) {
+ return nullptr;
}
+ gainmap->bitmap = std::move(skBitmap);
+ return gainmap;
}
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/libs/hwui/Gainmap.h
index 24275e002c7f..3bc183ab854f 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
+++ b/libs/hwui/Gainmap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+#pragma once
-import android.app.Activity;
-import android.os.Bundle;
+#include <SkGainmapInfo.h>
+#include <SkImage.h>
+#include <hwui/Bitmap.h>
+#include <utils/LightRefBase.h>
-public class NonResizeableActivity extends Activity {
+namespace android::uirenderer {
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.activity_non_resizeable);
- }
-}
+class Gainmap : public LightRefBase<Gainmap> {
+public:
+ SkGainmapInfo info;
+ sk_sp<Bitmap> bitmap;
+ static sp<Gainmap> allocateHardwareGainmap(const sp<Gainmap>& srcGainmap);
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index c24cabb287de..b7e99994355c 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,8 +22,11 @@
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrDirectContext.h>
+#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkImage.h>
+#include <SkImageInfo.h>
+#include <SkRefCnt.h>
#include <gui/TraceUtils.h>
#include <utils/GLUtils.h>
#include <utils/NdkUtils.h>
@@ -39,6 +42,8 @@
namespace android::uirenderer {
+static constexpr auto kThreadTimeout = 60000_ms;
+
class AHBUploader;
// This helper uploader classes allows us to upload using either EGL or Vulkan using the same
// interface.
@@ -77,7 +82,7 @@ public:
}
void postIdleTimeoutCheck() {
- mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
+ mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); });
}
protected:
@@ -94,7 +99,7 @@ private:
bool shouldTimeOutLocked() {
nsecs_t durationSince = systemTime() - mLastUpload;
- return durationSince > 2000_ms;
+ return durationSince > kThreadTimeout;
}
void idleTimeoutCheck() {
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 81057a24c29c..00ee99648889 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -17,6 +17,9 @@
#pragma once
#include <hwui/Bitmap.h>
+#include <SkRefCnt.h>
+
+class SkBitmap;
namespace android::uirenderer {
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 9053c1240957..fc3118ae32dd 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -20,6 +20,8 @@
#include "utils/Color.h"
#include "utils/MathUtils.h"
+#include <SkBlendMode.h>
+
#include <log/log.h>
namespace android {
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
new file mode 100644
index 000000000000..ca1312e75f4c
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#include "MemoryPolicy.h"
+
+#include <android-base/properties.h>
+
+#include <optional>
+#include <string_view>
+
+#include "Properties.h"
+
+namespace android::uirenderer {
+
+constexpr static MemoryPolicy sDefaultMemoryPolicy;
+constexpr static MemoryPolicy sPersistentOrSystemPolicy{
+ .contextTimeout = 10_s,
+ .useAlternativeUiHidden = true,
+};
+constexpr static MemoryPolicy sLowRamPolicy{
+ .useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
+};
+constexpr static MemoryPolicy sExtremeLowRam{
+ .initialMaxSurfaceAreaScale = 0.2f,
+ .surfaceSizeMultiplier = 5 * 4.0f,
+ .backgroundRetentionPercent = 0.2f,
+ .contextTimeout = 5_s,
+ .minimumResourceRetention = 1_s,
+ .useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
+ .releaseContextOnStoppedOnly = true,
+};
+
+const MemoryPolicy& loadMemoryPolicy() {
+ if (Properties::isSystemOrPersistent) {
+ return sPersistentOrSystemPolicy;
+ }
+ std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, "");
+ if (memoryPolicy == "default") {
+ return sDefaultMemoryPolicy;
+ }
+ if (memoryPolicy == "lowram") {
+ return sLowRamPolicy;
+ }
+ if (memoryPolicy == "extremelowram") {
+ return sExtremeLowRam;
+ }
+
+ if (Properties::isLowRam) {
+ return sLowRamPolicy;
+ }
+ return sDefaultMemoryPolicy;
+}
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
new file mode 100644
index 000000000000..41ced8cebf83
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "utils/TimeUtils.h"
+
+namespace android::uirenderer {
+
+// Values mirror those from ComponentCallbacks2.java
+enum class TrimLevel {
+ COMPLETE = 80,
+ MODERATE = 60,
+ BACKGROUND = 40,
+ UI_HIDDEN = 20,
+ RUNNING_CRITICAL = 15,
+ RUNNING_LOW = 10,
+ RUNNING_MODERATE = 5,
+};
+
+struct MemoryPolicy {
+ // The initial scale factor applied to the display resolution. The default is 1, but
+ // lower values may be used to start with a smaller initial cache size. The cache will
+ // be adjusted if larger frames are actually rendered
+ float initialMaxSurfaceAreaScale = 1.0f;
+ // The foreground cache size multiplier. The surface area of the screen will be multiplied
+ // by this
+ float surfaceSizeMultiplier = 12.0f * 4.0f;
+ // How much of the foreground cache size should be preserved when going into the background
+ float backgroundRetentionPercent = 0.5f;
+ // How long after the last renderer goes away before the GPU context is released. A value
+ // of 0 means only drop the context on background TRIM signals
+ nsecs_t contextTimeout = 10_s;
+ // The minimum amount of time to hold onto items in the resource cache
+ // The actual time used will be the max of this & when frames were actually rendered
+ nsecs_t minimumResourceRetention = 10_s;
+ // If false, use only TRIM_UI_HIDDEN to drive background cache limits;
+ // If true, use all signals (such as all contexts are stopped) to drive the limits
+ bool useAlternativeUiHidden = true;
+ // Whether or not to only purge scratch resources when triggering UI Hidden or background
+ // collection
+ bool purgeScratchOnly = true;
+ // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+ bool releaseContextOnStoppedOnly = false;
+};
+
+const MemoryPolicy& loadMemoryPolicy();
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
new file mode 100644
index 000000000000..e59bc9565a59
--- /dev/null
+++ b/libs/hwui/Mesh.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#include "Mesh.h"
+
+#include <GLES/gl.h>
+#include <SkMesh.h>
+
+#include "SafeMath.h"
+
+static size_t min_vcount_for_mode(SkMesh::Mode mode) {
+ switch (mode) {
+ case SkMesh::Mode::kTriangles:
+ return 3;
+ case SkMesh::Mode::kTriangleStrip:
+ return 3;
+ }
+}
+
+// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
+std::tuple<bool, SkString> Mesh::validate() {
+#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
+ if (!mMeshSpec) {
+ FAIL_MESH_VALIDATE("MeshSpecification is required.");
+ }
+ if (mVertexBufferData.empty()) {
+ FAIL_MESH_VALIDATE("VertexBuffer is required.");
+ }
+
+ auto meshStride = mMeshSpec->stride();
+ auto meshMode = SkMesh::Mode(mMode);
+ SafeMath sm;
+ size_t vsize = sm.mul(meshStride, mVertexCount);
+ if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+ FAIL_MESH_VALIDATE(
+ "The vertex buffer offset and vertex count reads beyond the end of the"
+ " vertex buffer.");
+ }
+
+ if (mVertexOffset % meshStride != 0) {
+ FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
+ mVertexOffset, meshStride);
+ }
+
+ if (size_t uniformSize = mMeshSpec->uniformSize()) {
+ if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+ FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
+ mBuilder->fUniforms->size(), uniformSize);
+ }
+ }
+
+ auto modeToStr = [](SkMesh::Mode m) {
+ switch (m) {
+ case SkMesh::Mode::kTriangles:
+ return "triangles";
+ case SkMesh::Mode::kTriangleStrip:
+ return "triangle-strip";
+ }
+ };
+ if (!mIndexBufferData.empty()) {
+ if (mIndexCount < min_vcount_for_mode(meshMode)) {
+ FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
+ modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+ }
+ size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
+ if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+ FAIL_MESH_VALIDATE(
+ "The index buffer offset and index count reads beyond the end of the"
+ " index buffer.");
+ }
+ // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
+ if (!SkIsAlign2(mIndexOffset)) {
+ FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
+ }
+ } else {
+ if (mVertexCount < min_vcount_for_mode(meshMode)) {
+ FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
+ modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+ }
+ SkASSERT(!fICount);
+ SkASSERT(!fIOffset);
+ }
+
+ if (!sm.ok()) {
+ FAIL_MESH_VALIDATE("Overflow");
+ }
+#undef FAIL_MESH_VALIDATE
+ return {true, {}};
+}
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
new file mode 100644
index 000000000000..13e3c8e7bf77
--- /dev/null
+++ b/libs/hwui/Mesh.h
@@ -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.
+ */
+
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <GrDirectContext.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+class MeshUniformBuilder {
+public:
+ struct MeshUniform {
+ template <typename T>
+ std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+ const T& val) {
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ } else if (sizeof(val) != fVar->sizeInBytes()) {
+ LOG_FATAL("Incorrect value size");
+ } else {
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, &val, sizeof(val));
+ }
+ }
+
+ MeshUniform& operator=(const SkMatrix& val) {
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+ LOG_FATAL("Incorrect value size");
+ } else {
+ float* data = reinterpret_cast<float*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ data[0] = val.get(0);
+ data[1] = val.get(3);
+ data[2] = val.get(6);
+ data[3] = val.get(1);
+ data[4] = val.get(4);
+ data[5] = val.get(7);
+ data[6] = val.get(2);
+ data[7] = val.get(5);
+ data[8] = val.get(8);
+ }
+ return *this;
+ }
+
+ template <typename T>
+ bool set(const T val[], const int count) {
+ static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+ if (!fVar) {
+ LOG_FATAL("Assigning to missing variable");
+ return false;
+ } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+ LOG_FATAL("Incorrect value size");
+ return false;
+ } else {
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, val, sizeof(T) * count);
+ }
+ return true;
+ }
+
+ MeshUniformBuilder* fOwner;
+ const SkRuntimeEffect::Uniform* fVar;
+ };
+ MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+ explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+ fMeshSpec = sk_sp(meshSpec);
+ fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
+ }
+
+ sk_sp<SkData> fUniforms;
+
+private:
+ void* writableUniformData() {
+ if (!fUniforms->unique()) {
+ fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+ }
+ return fUniforms->writable_data();
+ }
+
+ sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+class Mesh {
+public:
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
+ std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
+ std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+ : mMeshSpec(meshSpec)
+ , mMode(mode)
+ , mVertexBufferData(std::move(vertexBufferData))
+ , mVertexCount(vertexCount)
+ , mVertexOffset(vertexOffset)
+ , mBuilder(std::move(builder))
+ , mBounds(bounds) {}
+
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
+ std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
+ std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
+ std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+ : mMeshSpec(meshSpec)
+ , mMode(mode)
+ , mVertexBufferData(std::move(vertexBufferData))
+ , mVertexCount(vertexCount)
+ , mVertexOffset(vertexOffset)
+ , mIndexBufferData(std::move(indexBuffer))
+ , mIndexCount(indexCount)
+ , mIndexOffset(indexOffset)
+ , mBuilder(std::move(builder))
+ , mBounds(bounds) {}
+
+ Mesh(Mesh&&) = default;
+
+ Mesh& operator=(Mesh&&) = default;
+
+ [[nodiscard]] std::tuple<bool, SkString> validate();
+
+ void updateSkMesh(GrDirectContext* context) const {
+ GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
+ if (context) {
+ genId = context->directContextID();
+ }
+
+ if (mIsDirty || genId != mGenerationId) {
+ auto vb = SkMesh::MakeVertexBuffer(
+ context, reinterpret_cast<const void*>(mVertexBufferData.data()),
+ mVertexBufferData.size());
+ auto meshMode = SkMesh::Mode(mMode);
+ if (!mIndexBufferData.empty()) {
+ auto ib = SkMesh::MakeIndexBuffer(
+ context, reinterpret_cast<const void*>(mIndexBufferData.data()),
+ mIndexBufferData.size());
+ mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+ ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
+ mBounds)
+ .mesh;
+ } else {
+ mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+ mBuilder->fUniforms, mBounds)
+ .mesh;
+ }
+ mIsDirty = false;
+ mGenerationId = genId;
+ }
+ }
+
+ SkMesh& getSkMesh() const {
+ LOG_FATAL_IF(mIsDirty,
+ "Attempt to obtain SkMesh when Mesh is dirty, did you "
+ "forget to call updateSkMesh with a GrDirectContext? "
+ "Defensively creating a CPU mesh");
+ return mMesh;
+ }
+
+ void markDirty() { mIsDirty = true; }
+
+ MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+
+private:
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ int mMode = 0;
+
+ std::vector<uint8_t> mVertexBufferData;
+ size_t mVertexCount = 0;
+ size_t mVertexOffset = 0;
+
+ std::vector<uint8_t> mIndexBufferData;
+ size_t mIndexCount = 0;
+ size_t mIndexOffset = 0;
+
+ std::unique_ptr<MeshUniformBuilder> mBuilder;
+ SkRect mBounds{};
+
+ mutable SkMesh mMesh{};
+ mutable bool mIsDirty = true;
+ mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+};
+#endif // MESH_H_
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5a67eb9935dd..9df6822b4867 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -82,11 +82,17 @@ bool Properties::isolatedProcess = false;
int Properties::contextPriority = 0;
float Properties::defaultSdrWhitePoint = 200.f;
-bool Properties::useHintManager = true;
+bool Properties::useHintManager = false;
int Properties::targetCpuTimePercentage = 70;
bool Properties::enableWebViewOverlays = true;
+bool Properties::isHighEndGfx = true;
+bool Properties::isLowRam = false;
+bool Properties::isSystemOrPersistent = false;
+
+float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -134,16 +140,23 @@ 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_TRACING_ENABLED, false));
+ SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
+ base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
- useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true);
+ useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, false);
targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
+ auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str());
+ if (hdrHeadroom >= 1.f) {
+ maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f);
+ }
+
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2f8c67903a8b..24e206bbc3b1 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,9 +143,32 @@ enum DebugLevel {
#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
/**
- * Allows to record Skia drawing commands with systrace.
+ * Allows broad recording of Skia drawing commands.
+ *
+ * If disabled, a very minimal set of trace events *may* be recorded.
+ * If enabled, a much broader set of trace events *may* be recorded.
+ *
+ * In either case, trace events are only recorded if an appropriately configured tracing session is
+ * active.
+ *
+ * Use debug.hwui.skia_use_perfetto_track_events to determine if ATrace (default) or Perfetto is
+ * used as the tracing backend.
+ */
+#define PROPERTY_SKIA_TRACING_ENABLED "debug.hwui.skia_tracing_enabled"
+
+/**
+ * Switches Skia's tracing to use Perfetto's Track Event system instead of ATrace.
+ *
+ * If disabled, ATrace will be used by default, which will record trace events from any of Skia's
+ * tracing categories if overall system tracing is active and the "gfx" and "view" ATrace categories
+ * are enabled.
+ *
+ * If enabled, then Perfetto's Track Event system will be used instead, which will only record if an
+ * active Perfetto tracing session is targeting the correct apps and Skia tracing categories with
+ * the Track Event data source enabled. This approach may be used to selectively filter out
+ * undesired Skia tracing categories, and events will contain more data fields.
*/
-#define PROPERTY_SKIA_ATRACE_ENABLED "debug.hwui.skia_atrace_enabled"
+#define PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS "debug.hwui.skia_use_perfetto_track_events"
/**
* Defines how many frames in a sequence to capture.
@@ -193,6 +216,10 @@ enum DebugLevel {
*/
#define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled"
+#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+
+#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -292,16 +319,29 @@ public:
static bool enableWebViewOverlays;
+ static bool isHighEndGfx;
+ static bool isLowRam;
+ static bool isSystemOrPersistent;
+
+ static float maxHdrHeadroomOn8bit;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
static void setIsHighEndGfx(bool isHighEndGfx) {
+ Properties::isHighEndGfx = isHighEndGfx;
stretchEffectBehavior = isHighEndGfx ?
StretchEffectBehavior::ShaderHWUI :
StretchEffectBehavior::UniformScale;
}
+ static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; }
+
+ static void setIsSystemOrPersistent(bool isSystemOrPersistent) {
+ Properties::isSystemOrPersistent = isSystemOrPersistent;
+ }
+
/**
* Used for testing. Typical configuration of stretch behavior is done
* through setIsHighEndGfx
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index a3ba88e4ee8a..045de35c1d97 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,12 +16,28 @@
#include "Readback.h"
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColorSpace.h>
+#include <SkImage.h>
+#include <SkImageInfo.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkSamplingOptions.h>
+#include <SkSurface.h>
+#include "include/gpu/GpuTypes.h" // from Skia
+#include <gui/TraceUtils.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <shaders/shaders.h>
#include <sync/sync.h>
#include <system/window.h>
-#include <gui/TraceUtils.h>
#include "DeferredLayerUpdater.h"
#include "Properties.h"
+#include "Tonemapper.h"
#include "hwui/Bitmap.h"
#include "pipeline/skia/LayerDrawable.h"
#include "renderthread/EglManager.h"
@@ -37,8 +53,7 @@ namespace uirenderer {
#define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom)
-CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRect,
- SkBitmap* bitmap) {
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) {
ATRACE_CALL();
// Setup the source
AHardwareBuffer* rawSourceBuffer;
@@ -51,44 +66,57 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
// Really this shouldn't ever happen, but better safe than sorry.
if (err == UNKNOWN_TRANSACTION) {
ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?");
- return copySurfaceIntoLegacy(window, inSrcRect, bitmap);
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect),
windowTransform);
if (err != NO_ERROR) {
ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
if (rawSourceBuffer == nullptr) {
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
+ return request->onCopyFinished(CopyResult::SourceEmpty);
}
UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
AHardwareBuffer_Desc description;
AHardwareBuffer_describe(sourceBuffer.get(), &description);
if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
+ {
+ ATRACE_NAME("sync_wait");
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
+ ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ return request->onCopyFinished(CopyResult::Timeout);
+ }
}
- sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
- static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+ int32_t dataspace = ANativeWindow_getBuffersDataSpace(window);
+
+ // If the application is not updating the Surface themselves, e.g., another
+ // process is producing buffers for the application to display, then
+ // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab
+ // the dataspace from buffer metadata instead, if it exists.
+ if (dataspace == 0) {
+ dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get());
+ }
+
+ sk_sp<SkColorSpace> colorSpace =
+ DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
if (!image.get()) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
sk_sp<GrDirectContext> grContext = mRenderThread.requireGrContext();
- SkRect srcRect = inSrcRect.toSkRect();
+ SkRect srcRect = request->srcRect.toSkRect();
SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height);
SkISize imageWH = SkISize::Make(description.width, description.height);
@@ -136,23 +164,26 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect),
SK_RECT_ARGS(textureRect));
if (!srcRect.intersect(textureRect)) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
+ SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
+ SkBitmap* bitmap = &skBitmap;
sk_sp<SkSurface> tmpSurface =
- SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+ SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+ tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
@@ -211,6 +242,10 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
auto constraint =
hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+
+ static constexpr float kMaxLuminanceNits = 4000.f;
+ tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint);
+
canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
canvas->restore();
@@ -223,52 +258,13 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
!tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) ||
!tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
ALOGW("Unable to convert content into the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
bitmap->notifyPixelsChanged();
- return CopyResult::Success;
-}
-
-CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect,
- SkBitmap* bitmap) {
- // Setup the source
- AHardwareBuffer* rawSourceBuffer;
- int rawSourceFence;
- Matrix4 texTransform;
- status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence,
- texTransform.data);
- base::unique_fd sourceFence(rawSourceFence);
- texTransform.invalidateType();
- if (err != NO_ERROR) {
- ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
- }
- if (rawSourceBuffer == nullptr) {
- ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
- }
-
- UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(sourceBuffer.get(), &description);
- if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
- ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
- }
-
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
- }
-
- sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
- static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
- sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
- return copyImageInto(image, srcRect, bitmap);
+ return request->onCopyFinished(CopyResult::Success);
}
CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
@@ -306,14 +302,14 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap
CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
SkBitmap* bitmap) {
ATRACE_CALL();
+ if (!image.get()) {
+ return CopyResult::UnknownError;
+ }
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
mRenderThread.requireGlContext();
} else {
mRenderThread.requireVkContext();
}
- if (!image.get()) {
- return CopyResult::UnknownError;
- }
int imgWidth = image->width();
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
@@ -351,14 +347,17 @@ bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect*
* software buffer.
*/
sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- SkBudgeted::kYes, bitmap->info(), 0,
+ skgpu::Budgeted::kYes,
+ bitmap->info(),
+ 0,
kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+ tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index d0d748ff5c16..a092d472abf0 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -16,11 +16,16 @@
#pragma once
+#include <SkRefCnt.h>
+
+#include "CopyRequest.h"
#include "Matrix.h"
#include "Rect.h"
#include "renderthread/RenderThread.h"
-#include <SkBitmap.h>
+class SkBitmap;
+class SkImage;
+struct SkRect;
namespace android {
class Bitmap;
@@ -31,23 +36,13 @@ namespace uirenderer {
class DeferredLayerUpdater;
class Layer;
-// Keep in sync with PixelCopy.java codes
-enum class CopyResult {
- Success = 0,
- UnknownError = 1,
- Timeout = 2,
- SourceEmpty = 3,
- SourceInvalid = 4,
- DestinationInvalid = 5,
-};
-
class Readback {
public:
explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {}
/**
* Copies the surface's most recently queued buffer into the provided bitmap.
*/
- CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
+ void copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request);
CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
CopyResult copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap);
@@ -55,7 +50,6 @@ public:
CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
private:
- CopyResult copySurfaceIntoLegacy(ANativeWindow* window, 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,
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index a285462eef74..0b58406516e3 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -15,13 +15,18 @@
*/
#include "RecordingCanvas.h"
-#include <hwui/Paint.h>
#include <GrRecordingContext.h>
+#include <SkMesh.h>
+#include <hwui/Paint.h>
+#include <log/log.h>
#include <experimental/type_traits>
+#include <utility>
+#include "Mesh.h"
#include "SkAndroidFrameworkUtils.h"
+#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkCanvasPriv.h"
#include "SkColor.h"
@@ -29,14 +34,21 @@
#include "SkDrawShadowInfo.h"
#include "SkImage.h"
#include "SkImageFilter.h"
+#include "SkImageInfo.h"
#include "SkLatticeIter.h"
-#include "SkMath.h"
+#include "SkPaint.h"
#include "SkPicture.h"
+#include "SkRRect.h"
#include "SkRSXform.h"
+#include "SkRect.h"
#include "SkRegion.h"
#include "SkTextBlob.h"
#include "SkVertices.h"
+#include "Tonemapper.h"
#include "VectorDrawable.h"
+#include "effects/GainmapRenderer.h"
+#include "include/gpu/GpuTypes.h" // from Skia
+#include "include/gpu/GrDirectContext.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
@@ -58,16 +70,24 @@ static void copy_v(void* dst) {}
template <typename S, typename... Rest>
static void copy_v(void* dst, const S* src, int n, Rest&&... rest) {
- SkASSERTF(((uintptr_t)dst & (alignof(S) - 1)) == 0,
- "Expected %p to be aligned for at least %zu bytes.", dst, alignof(S));
- sk_careful_memcpy(dst, src, n * sizeof(S));
- copy_v(SkTAddOffset<void>(dst, n * sizeof(S)), std::forward<Rest>(rest)...);
+ LOG_FATAL_IF(((uintptr_t)dst & (alignof(S) - 1)) != 0,
+ "Expected %p to be aligned for at least %zu bytes.",
+ dst, alignof(S));
+ // If n is 0, there is nothing to copy into dst from src.
+ if (n > 0) {
+ memcpy(dst, src, n * sizeof(S));
+ dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(dst) + n * sizeof(S));
+ }
+ // Repeat for the next items, if any
+ copy_v(dst, std::forward<Rest>(rest)...);
}
// Helper for getting back at arrays which have been copy_v'd together after an Op.
template <typename D, typename T>
static const D* pod(const T* op, size_t offset = 0) {
- return SkTAddOffset<const D>(op + 1, offset);
+ return reinterpret_cast<const D*>(
+ reinterpret_cast<const uint8_t*>(op + 1) + offset);
}
namespace {
@@ -266,7 +286,6 @@ struct DrawDRRect final : Op {
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const { c->drawDRRect(outer, inner, paint); }
};
-
struct DrawAnnotation final : Op {
static const auto kType = Type::DrawAnnotation;
DrawAnnotation(const SkRect& rect, SkData* value) : rect(rect), value(sk_ref_sp(value)) {}
@@ -315,9 +334,15 @@ struct DrawPicture final : Op {
struct DrawImage final : Op {
static const auto kType = Type::DrawImage;
- DrawImage(sk_sp<const SkImage>&& image, SkScalar x, SkScalar y,
- const SkSamplingOptions& sampling, const SkPaint* paint, BitmapPalette palette)
- : image(std::move(image)), x(x), y(y), sampling(sampling), palette(palette) {
+ DrawImage(DrawImagePayload&& payload, SkScalar x, SkScalar y, const SkSamplingOptions& sampling,
+ const SkPaint* paint)
+ : image(std::move(payload.image))
+ , x(x)
+ , y(y)
+ , sampling(sampling)
+ , palette(payload.palette)
+ , gainmap(std::move(payload.gainmapImage))
+ , gainmapInfo(payload.gainmapInfo) {
if (paint) {
this->paint = *paint;
}
@@ -327,17 +352,34 @@ struct DrawImage final : Op {
SkSamplingOptions sampling;
SkPaint paint;
BitmapPalette palette;
+ sk_sp<const SkImage> gainmap;
+ SkGainmapInfo gainmapInfo;
+
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImage(image.get(), x, y, sampling, &paint);
+ if (gainmap) {
+ SkRect src = SkRect::MakeWH(image->width(), image->height());
+ SkRect dst = SkRect::MakeXYWH(x, y, src.width(), src.height());
+ DrawGainmapBitmap(c, image, src, dst, sampling, &paint,
+ SkCanvas::kFast_SrcRectConstraint, gainmap, gainmapInfo);
+ } else {
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImage(image.get(), x, y, sampling, &newPaint);
+ }
}
};
struct DrawImageRect final : Op {
static const auto kType = Type::DrawImageRect;
- DrawImageRect(sk_sp<const SkImage>&& image, const SkRect* src, const SkRect& dst,
+ DrawImageRect(DrawImagePayload&& payload, const SkRect* src, const SkRect& dst,
const SkSamplingOptions& sampling, const SkPaint* paint,
- SkCanvas::SrcRectConstraint constraint, BitmapPalette palette)
- : image(std::move(image)), dst(dst), sampling(sampling), constraint(constraint)
- , palette(palette) {
+ SkCanvas::SrcRectConstraint constraint)
+ : image(std::move(payload.image))
+ , dst(dst)
+ , sampling(sampling)
+ , constraint(constraint)
+ , palette(payload.palette)
+ , gainmap(std::move(payload.gainmapImage))
+ , gainmapInfo(payload.gainmapInfo) {
this->src = src ? *src : SkRect::MakeIWH(this->image->width(), this->image->height());
if (paint) {
this->paint = *paint;
@@ -349,23 +391,32 @@ struct DrawImageRect final : Op {
SkPaint paint;
SkCanvas::SrcRectConstraint constraint;
BitmapPalette palette;
+ sk_sp<const SkImage> gainmap;
+ SkGainmapInfo gainmapInfo;
+
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint);
+ if (gainmap) {
+ DrawGainmapBitmap(c, image, src, dst, sampling, &paint, constraint, gainmap,
+ gainmapInfo);
+ } else {
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint);
+ }
}
};
struct DrawImageLattice final : Op {
static const auto kType = Type::DrawImageLattice;
- DrawImageLattice(sk_sp<const SkImage>&& image, int xs, int ys, int fs, const SkIRect& src,
- const SkRect& dst, SkFilterMode filter, const SkPaint* paint,
- BitmapPalette palette)
- : image(std::move(image))
+ DrawImageLattice(DrawImagePayload&& payload, int xs, int ys, int fs, const SkIRect& src,
+ const SkRect& dst, SkFilterMode filter, const SkPaint* paint)
+ : image(std::move(payload.image))
, xs(xs)
, ys(ys)
, fs(fs)
, src(src)
, dst(dst)
, filter(filter)
- , palette(palette) {
+ , palette(payload.palette) {
if (paint) {
this->paint = *paint;
}
@@ -378,13 +429,17 @@ struct DrawImageLattice final : Op {
SkPaint paint;
BitmapPalette palette;
void draw(SkCanvas* c, const SkMatrix&) const {
+ // TODO: Support drawing a gainmap 9-patch?
+
auto xdivs = pod<int>(this, 0), ydivs = pod<int>(this, xs * sizeof(int));
auto colors = (0 == fs) ? nullptr : pod<SkColor>(this, (xs + ys) * sizeof(int));
auto flags =
(0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>(
this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor));
- c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst,
- filter, &paint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter,
+ &newPaint);
}
};
@@ -448,6 +503,60 @@ struct DrawVertices final : Op {
c->drawVertices(vertices, mode, paint);
}
};
+struct DrawSkMesh final : Op {
+ static const auto kType = Type::DrawSkMesh;
+ DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+ : cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
+ isGpuBased = false;
+ }
+
+ const SkMesh& cpuMesh;
+ mutable SkMesh gpuMesh;
+ sk_sp<SkBlender> blender;
+ SkPaint paint;
+ mutable bool isGpuBased;
+ mutable GrDirectContext::DirectContextID contextId;
+ void draw(SkCanvas* c, const SkMatrix&) const {
+ GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+
+ GrDirectContext::DirectContextID id = directContext->directContextID();
+ if (!isGpuBased || contextId != id) {
+ sk_sp<SkMesh::VertexBuffer> vb =
+ SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+ if (!cpuMesh.indexBuffer()) {
+ gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
+ cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
+ cpuMesh.bounds())
+ .mesh;
+ } else {
+ sk_sp<SkMesh::IndexBuffer> ib =
+ SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+ gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
+ cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
+ cpuMesh.indexCount(), cpuMesh.indexOffset(),
+ cpuMesh.refUniforms(), cpuMesh.bounds())
+ .mesh;
+ }
+
+ isGpuBased = true;
+ contextId = id;
+ }
+
+ c->drawMesh(gpuMesh, blender, paint);
+ }
+};
+
+struct DrawMesh final : Op {
+ static const auto kType = Type::DrawMesh;
+ DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+ : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+ const Mesh& mesh;
+ sk_sp<SkBlender> blender;
+ SkPaint paint;
+
+ void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); }
+};
struct DrawAtlas final : Op {
static const auto kType = Type::DrawAtlas;
DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -554,7 +663,7 @@ public:
GrRecordingContext* directContext = c->recordingContext();
mLayerImageInfo =
c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
- mLayerSurface = SkSurface::MakeRenderTarget(directContext, SkBudgeted::kYes,
+ mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
mLayerImageInfo, 0,
kTopLeft_GrSurfaceOrigin, nullptr);
}
@@ -589,18 +698,23 @@ public:
};
}
+static constexpr inline bool is_power_of_two(int value) {
+ return (value & (value - 1)) == 0;
+}
+
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
size_t skip = SkAlignPtr(sizeof(T) + pod);
- SkASSERT(skip < (1 << 24));
+ LOG_FATAL_IF(skip >= (1 << 24));
if (fUsed + skip > fReserved) {
- static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2.");
+ static_assert(is_power_of_two(SKLITEDL_PAGE),
+ "This math needs updating for non-pow2.");
// Next greater multiple of SKLITEDL_PAGE.
fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1);
fBytes.realloc(fReserved);
LOG_ALWAYS_FATAL_IF(fBytes.get() == nullptr, "realloc(%zd) failed", fReserved);
}
- SkASSERT(fUsed + skip <= fReserved);
+ LOG_FATAL_IF((fUsed + skip) > fReserved);
auto op = (T*)(fBytes.get() + fUsed);
fUsed += skip;
new (op) T{std::forward<Args>(args)...};
@@ -712,27 +826,25 @@ void DisplayListData::drawPicture(const SkPicture* picture, const SkMatrix* matr
const SkPaint* paint) {
this->push<DrawPicture>(0, picture, matrix, paint);
}
-void DisplayListData::drawImage(sk_sp<const SkImage> image, SkScalar x, SkScalar y,
- const SkSamplingOptions& sampling, const SkPaint* paint,
- BitmapPalette palette) {
- this->push<DrawImage>(0, std::move(image), x, y, sampling, paint, palette);
+void DisplayListData::drawImage(DrawImagePayload&& payload, SkScalar x, SkScalar y,
+ const SkSamplingOptions& sampling, const SkPaint* paint) {
+ this->push<DrawImage>(0, std::move(payload), x, y, sampling, paint);
}
-void DisplayListData::drawImageRect(sk_sp<const SkImage> image, const SkRect* src,
+void DisplayListData::drawImageRect(DrawImagePayload&& payload, const SkRect* src,
const SkRect& dst, const SkSamplingOptions& sampling,
- const SkPaint* paint, SkCanvas::SrcRectConstraint constraint,
- BitmapPalette palette) {
- this->push<DrawImageRect>(0, std::move(image), src, dst, sampling, paint, constraint, palette);
+ const SkPaint* paint, SkCanvas::SrcRectConstraint constraint) {
+ this->push<DrawImageRect>(0, std::move(payload), src, dst, sampling, paint, constraint);
}
-void DisplayListData::drawImageLattice(sk_sp<const SkImage> image, const SkCanvas::Lattice& lattice,
- const SkRect& dst, SkFilterMode filter, const SkPaint* paint,
- BitmapPalette palette) {
+void DisplayListData::drawImageLattice(DrawImagePayload&& payload, const SkCanvas::Lattice& lattice,
+ const SkRect& dst, SkFilterMode filter,
+ const SkPaint* paint) {
int xs = lattice.fXCount, ys = lattice.fYCount;
int fs = lattice.fRectTypes ? (xs + 1) * (ys + 1) : 0;
size_t bytes = (xs + ys) * sizeof(int) + fs * sizeof(SkCanvas::Lattice::RectType) +
fs * sizeof(SkColor);
- SkASSERT(lattice.fBounds);
- void* pod = this->push<DrawImageLattice>(bytes, std::move(image), xs, ys, fs, *lattice.fBounds,
- dst, filter, paint, palette);
+ LOG_FATAL_IF(!lattice.fBounds);
+ void* pod = this->push<DrawImageLattice>(bytes, std::move(payload), xs, ys, fs,
+ *lattice.fBounds, dst, filter, paint);
copy_v(pod, lattice.fXDivs, xs, lattice.fYDivs, ys, lattice.fColors, fs, lattice.fRectTypes,
fs);
}
@@ -759,6 +871,14 @@ void DisplayListData::drawPoints(SkCanvas::PointMode mode, size_t count, const S
void DisplayListData::drawVertices(const SkVertices* vert, SkBlendMode mode, const SkPaint& paint) {
this->push<DrawVertices>(0, vert, mode, paint);
}
+void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
+ const SkPaint& paint) {
+ this->push<DrawSkMesh>(0, mesh, blender, paint);
+}
+void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender,
+ const SkPaint& paint) {
+ this->push<DrawMesh>(0, mesh, blender, paint);
+}
void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
const SkColor colors[], int count, SkBlendMode xfermode,
const SkSamplingOptions& sampling, const SkRect* cull,
@@ -1035,57 +1155,55 @@ void RecordingCanvas::drawRippleDrawable(const skiapipeline::RippleDrawableParam
fDL->drawRippleDrawable(params);
}
-void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScalar y,
- const SkSamplingOptions& sampling, const SkPaint* paint,
- BitmapPalette palette) {
- fDL->drawImage(image, x, y, sampling, paint, palette);
+void RecordingCanvas::drawImage(DrawImagePayload&& payload, SkScalar x, SkScalar y,
+ const SkSamplingOptions& sampling, const SkPaint* paint) {
+ fDL->drawImage(std::move(payload), x, y, sampling, paint);
}
-void RecordingCanvas::drawImageRect(const sk_sp<SkImage>& image, const SkRect& src,
+void RecordingCanvas::drawImageRect(DrawImagePayload&& payload, const SkRect& src,
const SkRect& dst, const SkSamplingOptions& sampling,
- const SkPaint* paint, SrcRectConstraint constraint,
- BitmapPalette palette) {
- fDL->drawImageRect(image, &src, dst, sampling, paint, constraint, palette);
+ const SkPaint* paint, SrcRectConstraint constraint) {
+ fDL->drawImageRect(std::move(payload), &src, dst, sampling, paint, constraint);
}
-void RecordingCanvas::drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice,
- const SkRect& dst, SkFilterMode filter, const SkPaint* paint,
- BitmapPalette palette) {
- if (!image || dst.isEmpty()) {
+void RecordingCanvas::drawImageLattice(DrawImagePayload&& payload, const Lattice& lattice,
+ const SkRect& dst, SkFilterMode filter,
+ const SkPaint* paint) {
+ if (!payload.image || dst.isEmpty()) {
return;
}
SkIRect bounds;
Lattice latticePlusBounds = lattice;
if (!latticePlusBounds.fBounds) {
- bounds = SkIRect::MakeWH(image->width(), image->height());
+ bounds = SkIRect::MakeWH(payload.image->width(), payload.image->height());
latticePlusBounds.fBounds = &bounds;
}
- if (SkLatticeIter::Valid(image->width(), image->height(), latticePlusBounds)) {
- fDL->drawImageLattice(image, latticePlusBounds, dst, filter, paint, palette);
+ if (SkLatticeIter::Valid(payload.image->width(), payload.image->height(), latticePlusBounds)) {
+ fDL->drawImageLattice(std::move(payload), latticePlusBounds, dst, filter, paint);
} else {
SkSamplingOptions sampling(filter, SkMipmapMode::kNone);
- fDL->drawImageRect(image, nullptr, dst, sampling, paint, kFast_SrcRectConstraint, palette);
+ fDL->drawImageRect(std::move(payload), nullptr, dst, sampling, paint,
+ kFast_SrcRectConstraint);
}
}
void RecordingCanvas::onDrawImage2(const SkImage* img, SkScalar x, SkScalar y,
const SkSamplingOptions& sampling, const SkPaint* paint) {
- fDL->drawImage(sk_ref_sp(img), x, y, sampling, paint, BitmapPalette::Unknown);
+ fDL->drawImage(DrawImagePayload(img), x, y, sampling, paint);
}
void RecordingCanvas::onDrawImageRect2(const SkImage* img, const SkRect& src, const SkRect& dst,
const SkSamplingOptions& sampling, const SkPaint* paint,
SrcRectConstraint constraint) {
- fDL->drawImageRect(sk_ref_sp(img), &src, dst, sampling, paint, constraint,
- BitmapPalette::Unknown);
+ fDL->drawImageRect(DrawImagePayload(img), &src, dst, sampling, paint, constraint);
}
void RecordingCanvas::onDrawImageLattice2(const SkImage* img, const SkCanvas::Lattice& lattice,
const SkRect& dst, SkFilterMode filter,
const SkPaint* paint) {
- fDL->drawImageLattice(sk_ref_sp(img), lattice, dst, filter, paint, BitmapPalette::Unknown);
+ fDL->drawImageLattice(DrawImagePayload(img), lattice, dst, filter, paint);
}
void RecordingCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
@@ -1101,6 +1219,13 @@ void RecordingCanvas::onDrawVerticesObject(const SkVertices* vertices,
SkBlendMode mode, const SkPaint& paint) {
fDL->drawVertices(vertices, mode, paint);
}
+void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
+ const SkPaint& paint) {
+ fDL->drawMesh(mesh, blender, paint);
+}
+void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+ fDL->drawMesh(mesh, blender, paint);
+}
void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
const SkRect texs[], const SkColor colors[], int count,
SkBlendMode bmode, const SkSamplingOptions& sampling,
@@ -1119,5 +1244,14 @@ void RecordingCanvas::drawWebView(skiapipeline::FunctorDrawable* drawable) {
fDL->drawWebView(drawable);
}
+[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
+ LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
+ if (meshWrapper) {
+ return meshWrapper->getSkMesh();
+ } else {
+ return *mesh;
+ }
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 212b4e72dcb2..1f4ba5d6d557 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -16,23 +16,32 @@
#pragma once
+#include <SkCanvas.h>
+#include <SkCanvasVirtualEnforcer.h>
+#include <SkDrawable.h>
+#include <SkGainmapInfo.h>
+#include <SkNoDrawCanvas.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkRect.h>
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+
+#include <cstdlib>
+#include <utility>
+#include <vector>
+
#include "CanvasTransform.h"
+#include "Gainmap.h"
#include "hwui/Bitmap.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "utils/AutoMalloc.h"
#include "utils/Macros.h"
#include "utils/TypeLogic.h"
-#include "SkCanvas.h"
-#include "SkCanvasVirtualEnforcer.h"
-#include "SkDrawable.h"
-#include "SkNoDrawCanvas.h"
-#include "SkPaint.h"
-#include "SkPath.h"
-#include "SkRect.h"
-
-#include "pipeline/skia/AnimatedDrawables.h"
-
-#include <SkRuntimeEffect.h>
-#include <vector>
+enum class SkBlendMode;
+class SkRRect;
+class Mesh;
namespace android {
namespace uirenderer {
@@ -59,6 +68,44 @@ struct DisplayListOp {
static_assert(sizeof(DisplayListOp) == 4);
+class DrawMeshPayload {
+public:
+ explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
+ explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
+
+ [[nodiscard]] const SkMesh& getSkMesh() const;
+
+private:
+ const SkMesh* mesh = nullptr;
+ const Mesh* meshWrapper = nullptr;
+};
+
+struct DrawImagePayload {
+ explicit DrawImagePayload(Bitmap& bitmap)
+ : image(bitmap.makeImage()), palette(bitmap.palette()) {
+ if (bitmap.hasGainmap()) {
+ auto gainmap = bitmap.gainmap();
+ gainmapInfo = gainmap->info;
+ gainmapImage = gainmap->bitmap->makeImage();
+ }
+ }
+
+ explicit DrawImagePayload(const SkImage* image)
+ : image(sk_ref_sp(image)), palette(BitmapPalette::Unknown) {}
+
+ DrawImagePayload(const DrawImagePayload&) = default;
+ DrawImagePayload(DrawImagePayload&&) = default;
+ DrawImagePayload& operator=(const DrawImagePayload&) = default;
+ DrawImagePayload& operator=(DrawImagePayload&&) = default;
+ ~DrawImagePayload() = default;
+
+ sk_sp<SkImage> image;
+ BitmapPalette palette;
+
+ sk_sp<SkImage> gainmapImage;
+ SkGainmapInfo gainmapInfo;
+};
+
class RecordingCanvas;
class DisplayListData final {
@@ -109,19 +156,21 @@ private:
void drawRRect(const SkRRect&, const SkPaint&);
void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
+ void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+ void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&);
+
void drawAnnotation(const SkRect&, const char*, SkData*);
void drawDrawable(SkDrawable*, const SkMatrix*);
void drawPicture(const SkPicture*, const SkMatrix*, const SkPaint*);
void drawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&);
- void drawImage(sk_sp<const SkImage>, SkScalar, SkScalar, const SkSamplingOptions&,
- const SkPaint*, BitmapPalette palette);
- void drawImageNine(sk_sp<const SkImage>, const SkIRect&, const SkRect&, const SkPaint*);
- void drawImageRect(sk_sp<const SkImage>, const SkRect*, const SkRect&, const SkSamplingOptions&,
- const SkPaint*, SkCanvas::SrcRectConstraint, BitmapPalette palette);
- void drawImageLattice(sk_sp<const SkImage>, const SkCanvas::Lattice&, const SkRect&,
- SkFilterMode, const SkPaint*, BitmapPalette);
+ void drawImage(DrawImagePayload&&, SkScalar, SkScalar, const SkSamplingOptions&,
+ const SkPaint*);
+ void drawImageRect(DrawImagePayload&&, const SkRect*, const SkRect&, const SkSamplingOptions&,
+ const SkPaint*, SkCanvas::SrcRectConstraint);
+ void drawImageLattice(DrawImagePayload&&, const SkCanvas::Lattice&, const SkRect&, SkFilterMode,
+ const SkPaint*);
void drawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode,
const SkPaint&);
@@ -140,7 +189,7 @@ private:
template <typename Fn, typename... Args>
void map(const Fn[], Args...) const;
- SkAutoTMalloc<uint8_t> fBytes;
+ AutoTMalloc<uint8_t> fBytes;
size_t fUsed = 0;
size_t fReserved = 0;
@@ -188,14 +237,14 @@ public:
void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override;
- void drawImage(const sk_sp<SkImage>&, SkScalar left, SkScalar top, const SkSamplingOptions&,
- const SkPaint* paint, BitmapPalette pallete);
void drawRippleDrawable(const skiapipeline::RippleDrawableParams& params);
- void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst,
- const SkSamplingOptions&, const SkPaint*, SrcRectConstraint, BitmapPalette);
- void drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice, const SkRect& dst,
- SkFilterMode, const SkPaint* paint, BitmapPalette palette);
+ void drawImage(DrawImagePayload&&, SkScalar, SkScalar, const SkSamplingOptions&,
+ const SkPaint*);
+ void drawImageRect(DrawImagePayload&&, const SkRect&, const SkRect&, const SkSamplingOptions&,
+ const SkPaint*, SrcRectConstraint);
+ void drawImageLattice(DrawImagePayload&&, const Lattice& lattice, const SkRect&, SkFilterMode,
+ const SkPaint*);
void onDrawImage2(const SkImage*, SkScalar, SkScalar, const SkSamplingOptions&,
const SkPaint*) override;
@@ -208,10 +257,12 @@ public:
const SkPaint&) override;
void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+ void onDrawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) override;
void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
+ void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint);
void drawVectorDrawable(VectorDrawableRoot* tree);
void drawWebView(skiapipeline::FunctorDrawable*);
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 24443c8c9836..7170226037f9 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -71,9 +71,14 @@ public:
, right(rect.fRight)
, bottom(rect.fBottom) {}
- friend int operator==(const Rect& a, const Rect& b) { return !memcmp(&a, &b, sizeof(a)); }
+ friend int operator==(const Rect& a, const Rect& b) {
+ return a.left == b.left &&
+ a.top == b.top &&
+ a.right == b.right &&
+ a.bottom == b.bottom;
+ }
- friend int operator!=(const Rect& a, const Rect& b) { return memcmp(&a, &b, sizeof(a)); }
+ friend int operator!=(const Rect& a, const Rect& b) { return !(a == b); }
inline void clear() { left = top = right = bottom = 0.0f; }
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index da0476259b97..bdc48e91f6cb 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -16,7 +16,6 @@
#pragma once
-#include <SkCamera.h>
#include <SkMatrix.h>
#include <utils/LinearAllocator.h>
diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h
new file mode 100644
index 000000000000..4d6adf55c0cb
--- /dev/null
+++ b/libs/hwui/SafeMath.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef SkSafeMath_DEFINED
+#define SkSafeMath_DEFINED
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+// Copy of Skia's SafeMath API used to validate Mesh parameters to support
+// deferred creation of SkMesh instances on RenderThread.
+// SafeMath always check that a series of operations do not overflow.
+// This must be correct for all platforms, because this is a check for safety at runtime.
+
+class SafeMath {
+public:
+ SafeMath() = default;
+
+ bool ok() const { return fOK; }
+ explicit operator bool() const { return fOK; }
+
+ size_t mul(size_t x, size_t y) {
+ return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y);
+ }
+
+ size_t add(size_t x, size_t y) {
+ size_t result = x + y;
+ fOK &= result >= x;
+ return result;
+ }
+
+ /**
+ * Return a + b, unless this result is an overflow/underflow. In those cases, fOK will
+ * be set to false, and it is undefined what this returns.
+ */
+ int addInt(int a, int b) {
+ if (b < 0 && a < std::numeric_limits<int>::min() - b) {
+ fOK = false;
+ return a;
+ } else if (b > 0 && a > std::numeric_limits<int>::max() - b) {
+ fOK = false;
+ return a;
+ }
+ return a + b;
+ }
+
+ // These saturate to their results
+ static size_t Add(size_t x, size_t y) {
+ SafeMath tmp;
+ size_t sum = tmp.add(x, y);
+ return tmp.ok() ? sum : SIZE_MAX;
+ }
+
+ static size_t Mul(size_t x, size_t y) {
+ SafeMath tmp;
+ size_t prod = tmp.mul(x, y);
+ return tmp.ok() ? prod : SIZE_MAX;
+ }
+
+private:
+ uint32_t mul32(uint32_t x, uint32_t y) {
+ uint64_t bx = x;
+ uint64_t by = y;
+ uint64_t result = bx * by;
+ fOK &= result >> 32 == 0;
+ // Overflow information is capture in fOK. Return the result modulo 2^32.
+ return (uint32_t)result;
+ }
+
+ uint64_t mul64(uint64_t x, uint64_t y) {
+ if (x <= std::numeric_limits<uint64_t>::max() >> 32 &&
+ y <= std::numeric_limits<uint64_t>::max() >> 32) {
+ return x * y;
+ } else {
+ auto hi = [](uint64_t x) { return x >> 32; };
+ auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
+
+ uint64_t lx_ly = lo(x) * lo(y);
+ uint64_t hx_ly = hi(x) * lo(y);
+ uint64_t lx_hy = lo(x) * hi(y);
+ uint64_t hx_hy = hi(x) * hi(y);
+ uint64_t result = 0;
+ result = this->add(lx_ly, (hx_ly << 32));
+ result = this->add(result, (lx_hy << 32));
+ fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0;
+
+ return result;
+ }
+ }
+ bool fOK = true;
+};
+
+#endif // SkSafeMath_DEFINED
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index f9b3a8c12b2e..7a1276982d0a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -16,37 +16,46 @@
#include "SkiaCanvas.h"
-#include "CanvasProperty.h"
-#include "NinePatchUtils.h"
-#include "VectorDrawable.h"
-#include "hwui/Bitmap.h"
-#include "hwui/MinikinUtils.h"
-#include "hwui/PaintFilter.h"
-#include "pipeline/skia/AnimatedDrawables.h"
-#include "pipeline/skia/HolePunch.h"
-
#include <SkAndroidFrameworkUtils.h>
#include <SkAnimatedImage.h>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
#include <SkCanvasPriv.h>
#include <SkCanvasStateUtils.h>
#include <SkColorFilter.h>
-#include <SkDeque.h>
#include <SkDrawable.h>
#include <SkFont.h>
#include <SkGraphics.h>
#include <SkImage.h>
#include <SkImagePriv.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
#include <SkPicture.h>
+#include <SkRRect.h>
#include <SkRSXform.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
#include <SkShader.h>
-#include <SkTemplates.h>
#include <SkTextBlob.h>
#include <SkVertices.h>
+#include <log/log.h>
+#include <ui/FatVector.h>
#include <memory>
#include <optional>
#include <utility>
+#include "CanvasProperty.h"
+#include "Mesh.h"
+#include "NinePatchUtils.h"
+#include "VectorDrawable.h"
+#include "hwui/Bitmap.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "pipeline/skia/HolePunch.h"
+
namespace android {
using uirenderer::PaintUtils;
@@ -169,7 +178,7 @@ int SkiaCanvas::save(SaveFlags::Flags flags) {
// operation. It does this by explicitly saving off the clip & matrix state
// when requested and playing it back after the SkCanvas::restore.
void SkiaCanvas::restore() {
- const auto* rec = this->currentSaveRec();
+ const SaveRec* rec = this->currentSaveRec();
if (!rec) {
// Fast path - no record for this frame.
mCanvas->restore();
@@ -238,17 +247,20 @@ void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const Paint& paint) {
}
const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const {
- const SaveRec* rec = mSaveStack ? static_cast<const SaveRec*>(mSaveStack->back()) : nullptr;
+ const SaveRec* rec = (mSaveStack && !mSaveStack->empty())
+ ? static_cast<const SaveRec*>(&mSaveStack->back())
+ : nullptr;
int currentSaveCount = mCanvas->getSaveCount();
- SkASSERT(!rec || currentSaveCount >= rec->saveCount);
+ LOG_FATAL_IF(!(!rec || currentSaveCount >= rec->saveCount));
return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr;
}
-void SkiaCanvas::punchHole(const SkRRect& rect) {
+void SkiaCanvas::punchHole(const SkRRect& rect, float alpha) {
SkPaint paint = SkPaint();
- paint.setColor(0);
- paint.setBlendMode(SkBlendMode::kClear);
+ paint.setColor(SkColors::kBlack);
+ paint.setAlphaf(alpha);
+ paint.setBlendMode(SkBlendMode::kDstOut);
mCanvas->drawRRect(rect, paint);
}
@@ -269,13 +281,12 @@ void SkiaCanvas::recordPartialSave(SaveFlags::Flags flags) {
}
if (!mSaveStack) {
- mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8));
+ mSaveStack.reset(new std::deque<SaveRec>());
}
- SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
- rec->saveCount = mCanvas->getSaveCount();
- rec->saveFlags = flags;
- rec->clipIndex = mClipStack.size();
+ mSaveStack->emplace_back(mCanvas->getSaveCount(), // saveCount
+ flags, // saveFlags
+ mClipStack.size()); // clipIndex
}
template <typename T>
@@ -290,7 +301,7 @@ void SkiaCanvas::recordClip(const T& clip, SkClipOp op) {
// Applies and optionally removes all clips >= index.
void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) {
- SkASSERT(clipStartIndex <= mClipStack.size());
+ LOG_FATAL_IF(clipStartIndex > mClipStack.size());
const auto begin = mClipStack.cbegin() + clipStartIndex;
const auto end = mClipStack.cend();
@@ -306,7 +317,7 @@ void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) {
// If the current/post-restore save rec is also persisting clips, we
// leave them on the stack to be reapplied part of the next restore().
// Otherwise we're done and just pop them.
- const auto* rec = this->currentSaveRec();
+ const SaveRec* rec = this->currentSaveRec();
if (!rec || (rec->saveFlags & SaveFlags::Clip)) {
mClipStack.erase(begin, end);
}
@@ -562,6 +573,16 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons
applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
}
+void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+ GrDirectContext* context = nullptr;
+ auto recordingContext = mCanvas->recordingContext();
+ if (recordingContext) {
+ context = recordingContext->asDirectContext();
+ }
+ mesh.updateSkMesh(context);
+ mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+}
+
// ----------------------------------------------------------------------------
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
@@ -634,7 +655,7 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
texsPtr += 1;
y += dy;
}
- SkASSERT(texsPtr - texs == ptCount);
+ LOG_FATAL_IF((texsPtr - texs) != ptCount);
}
// cons up indices
@@ -657,14 +678,14 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
// bump to the next row
index += 1;
}
- SkASSERT(indexPtr - indices == indexCount);
+ LOG_FATAL_IF((indexPtr - indices) != indexCount);
}
// double-check that we have legal indices
-#ifdef SK_DEBUG
+#if !defined(NDEBUG)
{
for (int i = 0; i < indexCount; i++) {
- SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
+ LOG_FATAL_IF((unsigned)indices[i] >= (unsigned)ptCount);
}
}
#endif
@@ -706,10 +727,12 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa
numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
}
- SkAutoSTMalloc<25, SkCanvas::Lattice::RectType> flags(numFlags);
- SkAutoSTMalloc<25, SkColor> colors(numFlags);
+ // Most times, we do not have very many flags/colors, so the stack allocated part of
+ // FatVector will save us a heap allocation.
+ FatVector<SkCanvas::Lattice::RectType, 25> flags(numFlags);
+ FatVector<SkColor, 25> colors(numFlags);
if (numFlags > 0) {
- NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk, colors.get());
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.data(), numFlags, chunk, colors.data());
}
lattice.fBounds = nullptr;
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 715007cdcd3b..b785989f35cb 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -19,19 +19,21 @@
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
#include "DeferredLayerUpdater.h"
#endif
+#include <SkCanvas.h>
+
+#include <cassert>
+#include <deque>
+#include <optional>
+
#include "RenderNode.h"
#include "VectorDrawable.h"
+#include "hwui/BlurDrawLooper.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"
-#include <cassert>
-#include <optional>
+enum class SkBlendMode;
+class SkRRect;
namespace android {
@@ -63,7 +65,7 @@ public:
LOG_ALWAYS_FATAL("SkiaCanvas does not support enableZ");
}
- virtual void punchHole(const SkRRect& rect) override;
+ virtual void punchHole(const SkRRect& rect, float alpha) override;
virtual void setBitmap(const SkBitmap& bitmap) override;
@@ -117,8 +119,8 @@ public:
virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
const Paint& paint) override;
- virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
- const Paint& paint) override;
+ virtual void drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
+ const Paint& paint) override;
virtual void drawCircle(float x, float y, float radius, const Paint& paint) override;
virtual void drawOval(float left, float top, float right, float bottom,
@@ -127,6 +129,7 @@ public:
float sweepAngle, bool useCenter, const Paint& paint) override;
virtual void drawPath(const SkPath& path, const Paint& paint) override;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
@@ -206,6 +209,9 @@ private:
int saveCount;
SaveFlags::Flags saveFlags;
size_t clipIndex;
+
+ SaveRec(int saveCount, SaveFlags::Flags saveFlags, size_t clipIndex)
+ : saveCount(saveCount), saveFlags(saveFlags), clipIndex(clipIndex) {}
};
const SaveRec* currentSaveRec() const;
@@ -219,11 +225,11 @@ private:
class Clip;
- std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated
- SkCanvas* mCanvas; // we do NOT own this canvas, it must survive us
- // unless it is the same as mCanvasOwned.get()
- std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
- std::vector<Clip> mClipStack; // tracks persistent clips.
+ std::unique_ptr<SkCanvas> mCanvasOwned; // Might own a canvas we allocated.
+ SkCanvas* mCanvas; // We do NOT own this canvas, it must survive us
+ // unless it is the same as mCanvasOwned.get().
+ std::unique_ptr<std::deque<SaveRec>> mSaveStack; // Lazily allocated, tracks partial saves.
+ std::vector<Clip> mClipStack; // Tracks persistent clips.
sk_sp<PaintFilter> mPaintFilter;
};
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1ab218..b58f517834a3 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -16,12 +16,13 @@
#include "SkiaInterpolator.h"
-#include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
#include "include/private/SkFixed.h"
-#include "include/private/SkMalloc.h"
-#include "include/private/SkTo.h"
#include "src/core/SkTSearch.h"
+#include <log/log.h>
+
typedef int Dot14;
#define Dot14_ONE (1 << 14)
#define Dot14_HALF (1 << 13)
@@ -91,25 +92,23 @@ static float SkUnitCubicInterp(float value, float bx, float by, float cx, float
SkiaInterpolatorBase::SkiaInterpolatorBase() {
fStorage = nullptr;
fTimes = nullptr;
- SkDEBUGCODE(fTimesArray = nullptr;)
}
SkiaInterpolatorBase::~SkiaInterpolatorBase() {
if (fStorage) {
- sk_free(fStorage);
+ free(fStorage);
}
}
void SkiaInterpolatorBase::reset(int elemCount, int frameCount) {
fFlags = 0;
- fElemCount = SkToU8(elemCount);
- fFrameCount = SkToS16(frameCount);
+ fElemCount = static_cast<uint8_t>(elemCount);
+ fFrameCount = static_cast<int16_t>(frameCount);
fRepeat = SK_Scalar1;
if (fStorage) {
- sk_free(fStorage);
+ free(fStorage);
fStorage = nullptr;
fTimes = nullptr;
- SkDEBUGCODE(fTimesArray = nullptr);
}
}
@@ -205,7 +204,6 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T
SkiaInterpolator::SkiaInterpolator() {
INHERITED::reset(0, 0);
fValues = nullptr;
- SkDEBUGCODE(fScalarsArray = nullptr;)
}
SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
@@ -215,13 +213,12 @@ SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
void SkiaInterpolator::reset(int elemCount, int frameCount) {
INHERITED::reset(elemCount, frameCount);
- fStorage = sk_malloc_throw((sizeof(float) * elemCount + sizeof(SkTimeCode)) * frameCount);
+ size_t numBytes = (sizeof(float) * elemCount + sizeof(SkTimeCode)) * frameCount;
+ fStorage = malloc(numBytes);
+ LOG_ALWAYS_FATAL_IF(!fStorage, "Failed to allocate %zu bytes in %s",
+ numBytes, __func__);
fTimes = (SkTimeCode*)fStorage;
fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
-#ifdef SK_DEBUG
- fTimesArray = (SkTimeCode(*)[10])fTimes;
- fScalarsArray = (float(*)[10])fValues;
-#endif
}
#define SK_Fixed1Third (SK_Fixed1 / 3)
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index c03f502528be..9422cb526a8f 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -17,7 +17,8 @@
#ifndef SkiaInterpolator_DEFINED
#define SkiaInterpolator_DEFINED
-#include "include/private/SkTo.h"
+#include <cstddef>
+#include <cstdint>
class SkiaInterpolatorBase {
public:
@@ -46,7 +47,9 @@ public:
@param mirror If true, the odd repeats interpolate from the last key
frame and the first.
*/
- void setMirror(bool mirror) { fFlags = SkToU8((fFlags & ~kMirror) | (int)mirror); }
+ void setMirror(bool mirror) {
+ fFlags = static_cast<uint8_t>((fFlags & ~kMirror) | (int)mirror);
+ }
/** Set the repeat count. The repeat count may be fractional.
@param repeatCount Multiplies the total time by this scalar.
@@ -57,7 +60,7 @@ public:
@param reset If true, the odd repeats interpolate from the last key
frame and the first.
*/
- void setReset(bool reset) { fFlags = SkToU8((fFlags & ~kReset) | (int)reset); }
+ void setReset(bool reset) { fFlags = static_cast<uint8_t>((fFlags & ~kReset) | (int)reset); }
Result timeToT(uint32_t time, float* T, int* index, bool* exact) const;
@@ -75,9 +78,6 @@ protected:
};
SkTimeCode* fTimes; // pointer into fStorage
void* fStorage;
-#ifdef SK_DEBUG
- SkTimeCode (*fTimesArray)[10];
-#endif
};
class SkiaInterpolator : public SkiaInterpolatorBase {
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
new file mode 100644
index 000000000000..0d39f0e33298
--- /dev/null
+++ b/libs/hwui/Tonemapper.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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.
+ */
+
+#include "Tonemapper.h"
+
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+// libshaders only exists on Android devices
+#ifdef __ANDROID__
+#include <shaders/shaders.h>
+#endif
+
+#include "utils/Color.h"
+
+namespace android::uirenderer {
+
+namespace {
+
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
+class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
+public:
+ explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
+ : SkRuntimeEffectBuilder(std::move(effect)) {}
+
+ sk_sp<SkColorFilter> makeColorFilter() {
+ return this->effect()->makeColorFilter(this->uniforms());
+ }
+};
+
+static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
+ float maxDisplayLuminance,
+ float currentDisplayLuminanceNits,
+ float maxLuminance) {
+ auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+ auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
+ if (!runtimeEffect) {
+ LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+ }
+
+ ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+
+ const auto uniforms =
+ shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ currentDisplayLuminanceNits, maxLuminance);
+
+ for (const auto& uniform : uniforms) {
+ effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+ }
+
+ return effectBuilder.makeColorFilter();
+}
+
+static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
+ return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+ const auto transfer = extractTransfer(dataspace);
+
+ return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
+}
+
+static ui::Dataspace getDataspace(const SkImageInfo& image) {
+ return static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
+}
+#endif
+
+} // namespace
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint) {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
+ const auto sourceDataspace = getDataspace(source);
+ const auto destinationDataspace = getDataspace(destination);
+
+ if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
+ (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
+ const auto effect = shaders::LinearEffect{
+ .inputDataspace = sourceDataspace,
+ .outputDataspace = destinationDataspace,
+ .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
+ .fakeInputDataspace = destinationDataspace,
+ .type = shaders::LinearEffect::SkSLType::ColorFilter};
+ constexpr float kMaxDisplayBrightnessNits = 1000.f;
+ constexpr float kCurrentDisplayBrightnessNits = 500.f;
+ sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
+ effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
+ } else {
+ paint.setColorFilter(colorFilter);
+ }
+ }
+#else
+ return;
+#endif
+}
+
+} // namespace android::uirenderer
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/libs/hwui/Tonemapper.h
index baa1e6fdd1e9..c0d5325fa9f8 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
+++ b/libs/hwui/Tonemapper.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.testapp;
+#pragma once
-import android.app.Activity;
-import android.os.Bundle;
+#include <SkCanvas.h>
-public class SplitScreenSecondaryActivity extends Activity {
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.activity_splitscreen_secondary);
- }
-}
+namespace android::uirenderer {
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint);
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 983c7766273a..536ff781badc 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -21,9 +21,10 @@
#include <utils/Log.h>
#include "PathParser.h"
-#include "SkColorFilter.h"
+#include "SkImage.h"
#include "SkImageInfo.h"
-#include "SkShader.h"
+#include "SkSamplingOptions.h"
+#include "SkScalar.h"
#include "hwui/Paint.h"
#ifdef __ANDROID__
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 30bb04ae8361..c92654c479c1 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,6 +31,7 @@
#include <SkPath.h>
#include <SkPathMeasure.h>
#include <SkRect.h>
+#include <SkRefCnt.h>
#include <SkShader.h>
#include <SkSurface.h>
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 942c0506321c..770822a049b7 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -53,6 +53,7 @@ extern int register_android_graphics_FontFamily(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(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);
@@ -65,6 +66,7 @@ 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_graphics_text_GraphemeBreak(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -100,6 +102,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
{"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
{"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
+ {"android.graphics.PathIterator", REG_JNI(register_android_graphics_PathIterator)},
{"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)},
@@ -123,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.text.MeasuredText",
REG_JNI(register_android_graphics_text_MeasuredText)},
{"android.graphics.text.TextRunShaper", REG_JNI(register_android_graphics_text_TextShaper)},
+ {"android.graphics.text.GraphemeBreak",
+ REG_JNI(register_android_graphics_text_GraphemeBreak)},
{"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
};
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index bc6bc456ba5a..c442a7b1d17c 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -24,6 +24,11 @@
#include <GraphicsJNI.h>
#include <hwui/Bitmap.h>
+#include <SkBitmap.h>
+#include <SkColorSpace.h>
+#include <SkImageInfo.h>
+#include <SkRefCnt.h>
+#include <SkStream.h>
#include <utils/Color.h>
using namespace android;
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 2a939efed9bb..905b123076a2 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -23,7 +23,9 @@
#include <utils/Color.h>
#include <SkBitmap.h>
+#include <SkColorSpace.h>
#include <SkSurface.h>
+#include <SkRefCnt.h>
using namespace android;
diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp
index 70bd085343ce..cc79cba5e19c 100644
--- a/libs/hwui/apex/android_paint.cpp
+++ b/libs/hwui/apex/android_paint.cpp
@@ -19,6 +19,7 @@
#include "TypeCast.h"
#include <hwui/Paint.h>
+#include <SkBlendMode.h>
using namespace android;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd786bf..09ae7e78fe23 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -55,10 +55,12 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(JNIEnv* env);
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv*);
extern int register_android_graphics_Region(JNIEnv* env);
@@ -75,11 +77,15 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
+extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
#ifdef NDEBUG
#define REG_JNI(name) { name }
@@ -94,59 +100,66 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env);
};
#endif
-static const RegJNIRec gRegJNI[] = {
- REG_JNI(register_android_graphics_Canvas),
- // This needs to be before register_android_graphics_Graphics, or the latter
- // will not be able to find the jmethodID for ColorSpace.get().
- REG_JNI(register_android_graphics_ColorSpace),
- REG_JNI(register_android_graphics_Graphics),
- REG_JNI(register_android_graphics_Bitmap),
- REG_JNI(register_android_graphics_BitmapFactory),
- REG_JNI(register_android_graphics_BitmapRegionDecoder),
- REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
- REG_JNI(register_android_graphics_Camera),
- REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
- REG_JNI(register_android_graphics_CanvasProperty),
- REG_JNI(register_android_graphics_ColorFilter),
- REG_JNI(register_android_graphics_DrawFilter),
- REG_JNI(register_android_graphics_FontFamily),
- REG_JNI(register_android_graphics_HardwareRendererObserver),
- REG_JNI(register_android_graphics_ImageDecoder),
- REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
- REG_JNI(register_android_graphics_Interpolator),
- REG_JNI(register_android_graphics_MaskFilter),
- REG_JNI(register_android_graphics_Matrix),
- REG_JNI(register_android_graphics_Movie),
- REG_JNI(register_android_graphics_NinePatch),
- REG_JNI(register_android_graphics_Paint),
- REG_JNI(register_android_graphics_Path),
- REG_JNI(register_android_graphics_PathMeasure),
- REG_JNI(register_android_graphics_PathEffect),
- REG_JNI(register_android_graphics_Picture),
- REG_JNI(register_android_graphics_Region),
- REG_JNI(register_android_graphics_Shader),
- REG_JNI(register_android_graphics_RenderEffect),
- REG_JNI(register_android_graphics_TextureLayer),
- REG_JNI(register_android_graphics_Typeface),
- REG_JNI(register_android_graphics_YuvImage),
- REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
- REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
- REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
- REG_JNI(register_android_graphics_drawable_VectorDrawable),
- REG_JNI(register_android_graphics_fonts_Font),
- REG_JNI(register_android_graphics_fonts_FontFamily),
- REG_JNI(register_android_graphics_pdf_PdfDocument),
- REG_JNI(register_android_graphics_pdf_PdfEditor),
- REG_JNI(register_android_graphics_pdf_PdfRenderer),
- REG_JNI(register_android_graphics_text_MeasuredText),
- REG_JNI(register_android_graphics_text_LineBreaker),
- REG_JNI(register_android_graphics_text_TextShaper),
-
- REG_JNI(register_android_util_PathParser),
- REG_JNI(register_android_view_RenderNode),
- REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_ThreadedRenderer),
-};
+ static const RegJNIRec gRegJNI[] = {
+ REG_JNI(register_android_graphics_Canvas),
+ // This needs to be before register_android_graphics_Graphics, or the latter
+ // will not be able to find the jmethodID for ColorSpace.get().
+ REG_JNI(register_android_graphics_ColorSpace),
+ REG_JNI(register_android_graphics_Graphics),
+ REG_JNI(register_android_graphics_Bitmap),
+ REG_JNI(register_android_graphics_BitmapFactory),
+ REG_JNI(register_android_graphics_BitmapRegionDecoder),
+ REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
+ REG_JNI(register_android_graphics_Camera),
+ REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
+ REG_JNI(register_android_graphics_CanvasProperty),
+ REG_JNI(register_android_graphics_ColorFilter),
+ REG_JNI(register_android_graphics_DrawFilter),
+ REG_JNI(register_android_graphics_FontFamily),
+ REG_JNI(register_android_graphics_Gainmap),
+ REG_JNI(register_android_graphics_HardwareRendererObserver),
+ REG_JNI(register_android_graphics_ImageDecoder),
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
+ REG_JNI(register_android_graphics_Interpolator),
+ REG_JNI(register_android_graphics_MaskFilter),
+ REG_JNI(register_android_graphics_Matrix),
+ REG_JNI(register_android_graphics_Movie),
+ REG_JNI(register_android_graphics_NinePatch),
+ REG_JNI(register_android_graphics_Paint),
+ REG_JNI(register_android_graphics_Path),
+ REG_JNI(register_android_graphics_PathIterator),
+ REG_JNI(register_android_graphics_PathMeasure),
+ REG_JNI(register_android_graphics_PathEffect),
+ REG_JNI(register_android_graphics_Picture),
+ REG_JNI(register_android_graphics_Region),
+ REG_JNI(register_android_graphics_Shader),
+ REG_JNI(register_android_graphics_RenderEffect),
+ REG_JNI(register_android_graphics_TextureLayer),
+ REG_JNI(register_android_graphics_Typeface),
+ REG_JNI(register_android_graphics_YuvImage),
+ REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
+ REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
+ REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
+ REG_JNI(register_android_graphics_drawable_VectorDrawable),
+ REG_JNI(register_android_graphics_fonts_Font),
+ REG_JNI(register_android_graphics_fonts_FontFamily),
+ REG_JNI(register_android_graphics_pdf_PdfDocument),
+ REG_JNI(register_android_graphics_pdf_PdfEditor),
+ REG_JNI(register_android_graphics_pdf_PdfRenderer),
+ REG_JNI(register_android_graphics_text_MeasuredText),
+ REG_JNI(register_android_graphics_text_LineBreaker),
+ REG_JNI(register_android_graphics_text_TextShaper),
+ REG_JNI(register_android_graphics_text_GraphemeBreak),
+ REG_JNI(register_android_graphics_MeshSpecification),
+ REG_JNI(register_android_graphics_Mesh),
+
+ REG_JNI(register_android_util_PathParser),
+ REG_JNI(register_android_view_RenderNode),
+ REG_JNI(register_android_view_DisplayListCanvas),
+ REG_JNI(register_android_graphics_HardwareBufferRenderer),
+
+ REG_JNI(register_android_view_ThreadedRenderer),
+ };
} // namespace android
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index fdc97a4fd8ba..2dcbca8273e7 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -17,13 +17,19 @@
#pragma once
#include <SkAndroidFrameworkUtils.h>
+#include <SkBlendMode.h>
#include <SkCanvas.h>
-#include <SkPath.h>
-#include <SkRegion.h>
-#include <SkVertices.h>
+#include <SkClipOp.h>
#include <SkImage.h>
+#include <SkPaint.h>
+#include <SkPath.h>
#include <SkPicture.h>
+#include <SkRRect.h>
+#include <SkRect.h>
+#include <SkRegion.h>
#include <SkRuntimeEffect.h>
+#include <SkSamplingOptions.h>
+#include <SkVertices.h>
#include <log/log.h>
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
new file mode 100644
index 000000000000..bfe4eaf39e21
--- /dev/null
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GainmapRenderer.h"
+
+#include <SkGainmapShader.h>
+
+#include "Gainmap.h"
+#include "Rect.h"
+#include "utils/Trace.h"
+
+#ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
+#include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
+#endif
+
+namespace android::uirenderer {
+
+using namespace renderthread;
+
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+ // We should always have a known destination colorspace. If we don't we must be in some
+ // legacy mode where we're lost and also definitely not going to HDR
+ if (destColorspace == nullptr) {
+ return 1.f;
+ }
+
+ constexpr float GenericSdrWhiteNits = 203.f;
+ constexpr float maxPQLux = 10000.f;
+ constexpr float maxHLGLux = 1000.f;
+ skcms_TransferFunction destTF;
+ destColorspace->transferFn(&destTF);
+ if (skcms_TransferFunction_isPQish(&destTF)) {
+ return maxPQLux / GenericSdrWhiteNits;
+ } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+ return maxHLGLux / GenericSdrWhiteNits;
+ } else {
+#ifdef __ANDROID__
+ CanvasContext* context = CanvasContext::getActiveContext();
+ return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+ return 1.f;
+#endif
+ }
+}
+
+void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
+ const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
+ SkCanvas::SrcRectConstraint constraint,
+ const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
+ ATRACE_CALL();
+#ifdef __ANDROID__
+ auto destColorspace = c->imageInfo().refColorSpace();
+ float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
+ if (targetSdrHdrRatio > 1.f && gainmapImage) {
+ SkPaint gainmapPaint = *paint;
+ float sX = gainmapImage->width() / (float)image->width();
+ float sY = gainmapImage->height() / (float)image->height();
+ SkRect gainmapSrc = src;
+ // TODO: Tweak rounding?
+ gainmapSrc.fLeft *= sX;
+ gainmapSrc.fRight *= sX;
+ gainmapSrc.fTop *= sY;
+ gainmapSrc.fBottom *= sY;
+ auto shader =
+ SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+ gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
+ gainmapPaint.setShader(shader);
+ c->drawRect(dst, gainmapPaint);
+ } else
+#endif
+ c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
+}
+
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+ uniform shader base;
+ uniform shader gainmap;
+ uniform colorFilter workingSpaceToLinearSrgb;
+ uniform half4 logRatioMin;
+ uniform half4 logRatioMax;
+ uniform half4 gainmapGamma;
+ uniform half4 epsilonSdr;
+ uniform half4 epsilonHdr;
+ uniform half W;
+ uniform int gainmapIsAlpha;
+ uniform int gainmapIsRed;
+ uniform int singleChannel;
+ uniform int noGamma;
+
+ half4 toDest(half4 working) {
+ half4 ls = workingSpaceToLinearSrgb.eval(working);
+ vec3 dest = fromLinearSrgb(ls.rgb);
+ return half4(dest.r, dest.g, dest.b, ls.a);
+ }
+
+ half4 main(float2 coord) {
+ half4 S = base.eval(coord);
+ half4 G = gainmap.eval(coord);
+ if (gainmapIsAlpha == 1) {
+ G = half4(G.a, G.a, G.a, 1.0);
+ }
+ if (gainmapIsRed == 1) {
+ G = half4(G.r, G.r, G.r, 1.0);
+ }
+ if (singleChannel == 1) {
+ half L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.r, logRatioMax.r, G.r);
+ } else {
+ L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ } else {
+ half3 L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+ } else {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ }
+ }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+ static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+ auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+ if (buildResult.effect) {
+ return buildResult.effect.release();
+ } else {
+ LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+ }
+ }();
+ SkASSERT(effect);
+ return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+ return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+ sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+ SkRuntimeShaderBuilder mBuilder{mShader};
+ SkGainmapInfo mGainmapInfo;
+ std::mutex mUniformGuard;
+
+ void setupChildren(const sk_sp<const SkImage>& baseImage,
+ const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+ sk_sp<SkColorSpace> baseColorSpace =
+ baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+ // Determine the color space in which the gainmap math is to be applied.
+ sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+ // Create a color filter to transform from the base image's color space to the color space
+ // in which the gainmap is to be applied.
+ auto colorXformSdrToGainmap =
+ SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+ // The base image shader will convert into the color space in which the gainmap is applied.
+ auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+ ->makeWithColorFilter(colorXformSdrToGainmap);
+
+ // The gainmap image shader will ignore any color space that the gainmap has.
+ const SkMatrix gainmapRectToDstRect =
+ SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+ SkRect::MakeWH(baseImage->width(), baseImage->height()));
+ auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+ &gainmapRectToDstRect);
+
+ // Create a color filter to transform from the color space in which the gainmap is applied
+ // to the intermediate destination color space.
+ auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+ gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+ mBuilder.child("base") = std::move(baseImageShader);
+ mBuilder.child("gainmap") = std::move(gainmapImageShader);
+ mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+ }
+
+ void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo) {
+ const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+ const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+ const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+ gainmapInfo.fGainmapGamma.fG == 1.f &&
+ gainmapInfo.fGainmapGamma.fB == 1.f;
+ const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+ const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+ const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+ const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+ (colorTypeFlags == kGray_SkColorChannelFlag ||
+ colorTypeFlags == kAlpha_SkColorChannelFlag ||
+ colorTypeFlags == kRed_SkColorChannelFlag);
+ mBuilder.uniform("logRatioMin") = logRatioMin;
+ mBuilder.uniform("logRatioMax") = logRatioMax;
+ mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+ mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+ mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+ mBuilder.uniform("noGamma") = noGamma;
+ mBuilder.uniform("singleChannel") = singleChannel;
+ mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+ mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+ }
+
+ sk_sp<const SkData> build(float targetHdrSdrRatio) {
+ sk_sp<const SkData> uniforms;
+ {
+ // If we are called concurrently from multiple threads, we need to guard the call
+ // to writableUniforms() which mutates mUniform. This is otherwise safe because
+ // writeableUniforms() will make a copy if it's not unique before mutating
+ // This can happen if a BitmapShader is used on multiple canvas', such as a
+ // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+ std::lock_guard _lock(mUniformGuard);
+ const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ mBuilder.uniform("W") = W;
+ uniforms = mBuilder.uniforms();
+ }
+ return uniforms;
+ }
+
+public:
+ explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ mGainmapInfo = gainmapInfo;
+ setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+ setupGenericUniforms(gainmapImage, gainmapInfo);
+ }
+
+ static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+ image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+ auto callback =
+ [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+ -> sk_sp<const SkData> {
+ return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+ };
+ return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+ deferredHandler->mBuilder.children());
+ }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+ sampling);
+}
+
+#else // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return nullptr;
+}
+
+#endif // __ANDROID__
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
new file mode 100644
index 000000000000..4ed2445da17e
--- /dev/null
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkGainmapInfo.h>
+#include <SkImage.h>
+#include <SkPaint.h>
+
+#include "hwui/Bitmap.h"
+
+namespace android::uirenderer {
+
+void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
+ const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
+ SkCanvas::SrcRectConstraint constraint,
+ const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 67f47580a70f..b3eaa0ce5979 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -34,10 +34,19 @@
#include <binder/IServiceManager.h>
#endif
+#include <Gainmap.h>
#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkEncodedImageFormat.h>
+#include <SkHighContrastFilter.h>
+#include <SkImageEncoder.h>
#include <SkImagePriv.h>
+#include <SkJpegGainmapEncoder.h>
+#include <SkPixmap.h>
+#include <SkRect.h>
+#include <SkStream.h>
#include <SkWebpEncoder.h>
-#include <SkHighContrastFilter.h>
+
#include <limits>
namespace android {
@@ -450,6 +459,23 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr,
}
bool Bitmap::compress(JavaCompressFormat format, int32_t quality, SkWStream* stream) {
+#ifdef __ANDROID__ // TODO: This isn't built for host for some reason?
+ if (hasGainmap() && format == JavaCompressFormat::Jpeg) {
+ SkBitmap baseBitmap = getSkBitmap();
+ SkBitmap gainmapBitmap = gainmap()->bitmap->getSkBitmap();
+ if (gainmapBitmap.colorType() == SkColorType::kAlpha_8_SkColorType) {
+ SkBitmap greyGainmap;
+ auto greyInfo = gainmapBitmap.info().makeColorType(SkColorType::kGray_8_SkColorType);
+ greyGainmap.setInfo(greyInfo, gainmapBitmap.rowBytes());
+ greyGainmap.setPixelRef(sk_ref_sp(gainmapBitmap.pixelRef()), 0, 0);
+ gainmapBitmap = std::move(greyGainmap);
+ }
+ SkJpegEncoder::Options options{.fQuality = quality};
+ return SkJpegGainmapEncoder::EncodeHDRGM(stream, baseBitmap.pixmap(), options,
+ gainmapBitmap.pixmap(), options, gainmap()->info);
+ }
+#endif
+
SkBitmap skbitmap;
getSkBitmap(&skbitmap);
return compress(skbitmap, format, quality, stream);
@@ -488,4 +514,14 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format,
return SkEncodeImage(stream, bitmap, fm, quality);
}
+
+sp<uirenderer::Gainmap> Bitmap::gainmap() const {
+ LOG_ALWAYS_FATAL_IF(!hasGainmap(), "Bitmap doesn't have a gainmap");
+ return mGainmap;
+}
+
+void Bitmap::setGainmap(sp<uirenderer::Gainmap>&& gainmap) {
+ mGainmap = std::move(gainmap);
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 94a047c06ced..912d311c3678 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -19,10 +19,14 @@
#include <SkColorFilter.h>
#include <SkColorSpace.h>
#include <SkImage.h>
-#include <SkImage.h>
#include <SkImageInfo.h>
#include <SkPixelRef.h>
+#include <SkRefCnt.h>
#include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include <optional>
+
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
#include <android/hardware_buffer.h>
#endif
@@ -47,6 +51,7 @@ enum class BitmapPalette {
};
namespace uirenderer {
+class Gainmap;
namespace renderthread {
class RenderThread;
}
@@ -119,6 +124,11 @@ public:
void getBounds(SkRect* bounds) const;
bool isHardware() const { return mPixelStorageType == PixelStorageType::Hardware; }
+ bool hasGainmap() const { return mGainmap.get() != nullptr; }
+
+ sp<uirenderer::Gainmap> gainmap() const;
+
+ void setGainmap(sp<uirenderer::Gainmap>&& gainmap);
PixelStorageType pixelStorageType() const { return mPixelStorageType; }
@@ -193,6 +203,8 @@ private:
bool mHasHardwareMipMap = false;
+ sp<uirenderer::Gainmap> mGainmap;
+
union {
struct {
SkPixelRef* pixelRef;
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
index 270d24af99fd..8b52551fc107 100644
--- a/libs/hwui/hwui/BlurDrawLooper.cpp
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -15,6 +15,8 @@
*/
#include "BlurDrawLooper.h"
+#include <SkBlurTypes.h>
+#include <SkColorSpace.h>
#include <SkMaskFilter.h>
namespace android {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index b046f45d9c57..cd8af3d933b1 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -26,6 +26,7 @@
#include "hwui/PaintFilter.h"
#include <SkFontMetrics.h>
+#include <SkRRect.h>
namespace android {
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 82777646f3a2..44ee31d34d23 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -16,23 +16,25 @@
#pragma once
+#include <SaveFlags.h>
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkMatrix.h>
+#include <androidfw/ResourceTypes.h>
#include <cutils/compiler.h>
#include <utils/Functor.h>
-#include <SaveFlags.h>
-#include <androidfw/ResourceTypes.h>
#include "Properties.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "utils/Macros.h"
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkMatrix.h>
-
class SkAnimatedImage;
+enum class SkBlendMode;
class SkCanvasState;
+class SkRRect;
class SkRuntimeShaderBuilder;
class SkVertices;
+class Mesh;
namespace minikin {
class Font;
@@ -151,7 +153,7 @@ public:
LOG_ALWAYS_FATAL("Not supported");
}
- virtual void punchHole(const SkRRect& rect) = 0;
+ virtual void punchHole(const SkRRect& rect, float alpha) = 0;
// ----------------------------------------------------------------------------
// Canvas state operations
@@ -225,6 +227,7 @@ public:
float sweepAngle, bool useCenter, const Paint& paint) = 0;
virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0;
// Bitmap-based
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index dd68f825b61d..9a06be006dca 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -16,15 +16,32 @@
#include "ImageDecoder.h"
-#include <hwui/Bitmap.h>
-#include <log/log.h>
-
+#include <Gainmap.h>
+#include <SkAlphaType.h>
#include <SkAndroidCodec.h>
#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
+#include <SkCodec.h>
+#include <SkCodecAnimation.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
#include <SkEncodedOrigin.h>
+#include <SkImageInfo.h>
+#include <SkGainmapInfo.h>
+#include <SkMatrix.h>
#include <SkPaint.h>
+#include <SkPngChunkReader.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkSamplingOptions.h>
+#include <SkSize.h>
+#include <SkStream.h>
+#include <hwui/Bitmap.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <memory>
#undef LOG_TAG
#define LOG_TAG "ImageDecoder"
@@ -195,7 +212,7 @@ SkImageInfo ImageDecoder::getOutputInfo() const {
}
bool ImageDecoder::swapWidthHeight() const {
- return SkEncodedOriginSwapsWidthHeight(mCodec->codec()->getOrigin());
+ return SkEncodedOriginSwapsWidthHeight(getOrigin());
}
int ImageDecoder::width() const {
@@ -316,7 +333,7 @@ SkCodec::FrameInfo ImageDecoder::getCurrentFrameInfo() {
info.fFrameRect = SkIRect::MakeSize(dims);
}
- if (auto origin = mCodec->codec()->getOrigin(); origin != kDefault_SkEncodedOrigin) {
+ if (auto origin = getOrigin(); origin != kDefault_SkEncodedOrigin) {
if (SkEncodedOriginSwapsWidthHeight(origin)) {
dims = swapped(dims);
}
@@ -400,7 +417,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
// FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
SkBitmap tmp;
const bool scale = mDecodeSize != mTargetSize;
- const auto origin = mCodec->codec()->getOrigin();
+ const auto origin = getOrigin();
const bool handleOrigin = origin != kDefault_SkEncodedOrigin;
SkMatrix outputMatrix;
if (scale || handleOrigin || mCropRect) {
@@ -455,12 +472,15 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
}
+ ATRACE_BEGIN("getAndroidPixels");
auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+ ATRACE_END();
// The next call to decode() may not provide zero initialized memory.
mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
if (scale || handleOrigin || mCropRect) {
+ ATRACE_NAME("Handling scale/origin/crop");
SkBitmap scaledBm;
if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
return SkCodec::kInternalError;
@@ -478,3 +498,82 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
return result;
}
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
+ ATRACE_CALL();
+ SkGainmapInfo gainmapInfo;
+ std::unique_ptr<SkStream> gainmapStream;
+ {
+ ATRACE_NAME("getAndroidGainmap");
+ if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+ return SkCodec::kSuccess;
+ }
+ }
+ auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
+ if (!gainmapCodec) {
+ ALOGW("Failed to create codec for gainmap stream");
+ return SkCodec::kInvalidInput;
+ }
+ ImageDecoder decoder{std::move(gainmapCodec)};
+ // Gainmap inherits the origin of the containing image
+ decoder.mOverrideOrigin.emplace(getOrigin());
+ // Update mDecodeSize / mTargetSize for the overridden origin
+ decoder.setTargetSize(decoder.width(), decoder.height());
+ if (decoder.gray()) {
+ decoder.setOutColorType(kGray_8_SkColorType);
+ }
+
+ const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height();
+
+ if (isScaled) {
+ float scaleX = (float)mTargetSize.width() / width();
+ float scaleY = (float)mTargetSize.height() / height();
+ decoder.setTargetSize(decoder.width() * scaleX, decoder.height() * scaleY);
+ }
+
+ if (mCropRect) {
+ float sX = decoder.mTargetSize.width() / (float)mTargetSize.width();
+ float sY = decoder.mTargetSize.height() / (float)mTargetSize.height();
+ SkIRect crop = *mCropRect;
+ // TODO: Tweak rounding?
+ crop.fLeft *= sX;
+ crop.fRight *= sX;
+ crop.fTop *= sY;
+ crop.fBottom *= sY;
+ decoder.setCropRect(&crop);
+ }
+
+ SkImageInfo bitmapInfo = decoder.getOutputInfo();
+ if (bitmapInfo.colorType() == kGray_8_SkColorType) {
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
+
+ SkBitmap bm;
+ if (!bm.setInfo(bitmapInfo)) {
+ ALOGE("Failed to setInfo properly");
+ return SkCodec::kInternalError;
+ }
+
+ sk_sp<Bitmap> nativeBitmap;
+ if (isShared) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
+ bitmapInfo.height());
+ return SkCodec::kInternalError;
+ }
+
+ SkCodec::Result result = decoder.decode(bm.getPixels(), bm.rowBytes());
+ bm.setImmutable();
+
+ if (result == SkCodec::kSuccess) {
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ gainmap->info = gainmapInfo;
+ gainmap->bitmap = std::move(nativeBitmap);
+ destination->setGainmap(std::move(gainmap));
+ }
+
+ return result;
+}
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index cef2233fc371..b3781b52a418 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -17,9 +17,11 @@
#include <SkAndroidCodec.h>
#include <SkCodec.h>
+#include <SkColorSpace.h>
#include <SkImageInfo.h>
#include <SkPngChunkReader.h>
#include <SkRect.h>
+#include <SkRefCnt.h>
#include <SkSize.h>
#include <cutils/compiler.h>
@@ -77,6 +79,8 @@ public:
// Set whether the ImageDecoder should handle RestorePrevious frames.
void setHandleRestorePrevious(bool handle);
+ SkCodec::Result extractGainmap(Bitmap* destination, bool isShared);
+
private:
// State machine for keeping track of how to handle RestorePrevious (RP)
// frames in decode().
@@ -113,6 +117,7 @@ private:
RestoreState mRestoreState;
sk_sp<Bitmap> mRestoreFrame;
std::optional<SkIRect> mCropRect;
+ std::optional<SkEncodedOrigin> mOverrideOrigin;
ImageDecoder(const ImageDecoder&) = delete;
ImageDecoder& operator=(const ImageDecoder&) = delete;
@@ -122,6 +127,10 @@ private:
bool swapWidthHeight() const;
// Store/restore a frame if necessary. Returns false on error.
bool handleRestorePrevious(const SkImageInfo&, void* pixels, size_t rowBytes);
+
+ SkEncodedOrigin getOrigin() const {
+ return mOverrideOrigin.has_value() ? *mOverrideOrigin : mCodec->codec()->getOrigin();
+ }
};
} // namespace android
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 2db3ace1cd43..34cb4aef70d9 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -16,10 +16,13 @@
#include "MinikinSkia.h"
-#include <SkFontDescriptor.h>
#include <SkFont.h>
+#include <SkFontDescriptor.h>
#include <SkFontMetrics.h>
#include <SkFontMgr.h>
+#include <SkRect.h>
+#include <SkScalar.h>
+#include <SkStream.h>
#include <SkTypeface.h>
#include <log/log.h>
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 5a9d2508230e..3c67edc9a428 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -125,9 +125,14 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
}
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
- int weight, int italic) {
+ int weight, int italic, const Typeface* fallback) {
Typeface* result = new Typeface;
- result->fFontCollection.reset(new minikin::FontCollection(families));
+ if (fallback == nullptr) {
+ result->fFontCollection = minikin::FontCollection::create(std::move(families));
+ } else {
+ result->fFontCollection =
+ fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
+ }
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
@@ -191,8 +196,8 @@ void Typeface::setRobotoTypefaceForTest() {
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
- std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
- std::make_shared<minikin::FontFamily>(std::move(fonts)));
+ std::shared_ptr<minikin::FontCollection> collection =
+ minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
Typeface* hwTypeface = new Typeface();
hwTypeface->fFontCollection = collection;
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 0c3ef01ab26b..565136e53676 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -78,7 +78,8 @@ public:
Typeface* src, const std::vector<minikin::FontVariation>& variations);
static Typeface* createFromFamilies(
- std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic);
+ std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
+ const Typeface* fallback);
static void setDefault(const Typeface* face);
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index c40b858268be..373e893b9a25 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -21,8 +21,11 @@
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
+#include <SkEncodedImageFormat.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 5db0783cf83e..6ee7576651f2 100755..100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,41 +1,40 @@
#undef LOG_TAG
#define LOG_TAG "Bitmap"
+// #define LOG_NDEBUG 0
#include "Bitmap.h"
+#include <hwui/Bitmap.h>
+#include <hwui/Paint.h>
+
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "Gainmap.h"
+#include "GraphicsJNI.h"
+#include "HardwareBufferHelpers.h"
+#include "ScopedParcel.h"
#include "SkBitmap.h"
+#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkColor.h"
#include "SkColorSpace.h"
-#include "SkPixelRef.h"
-#include "SkImageEncoder.h"
+#include "SkData.h"
#include "SkImageInfo.h"
-#include "GraphicsJNI.h"
+#include "SkPaint.h"
+#include "SkPixmap.h"
+#include "SkPoint.h"
+#include "SkRefCnt.h"
#include "SkStream.h"
-#include "SkWebpEncoder.h"
-
+#include "SkTypes.h"
#include "android_nio_utils.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include <hwui/Paint.h>
-#include <hwui/Bitmap.h>
-#include <utils/Color.h>
#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
#include <android-base/unique_fd.h>
-#include <android/binder_parcel.h>
-#include <android/binder_parcel_jni.h>
-#include <android/binder_parcel_platform.h>
-#include <android/binder_parcel_utils.h>
-#include <private/android/AHardwareBufferHelpers.h>
-#include <cutils/ashmem.h>
-#include <dlfcn.h>
#include <renderthread/RenderProxy.h>
-#include <sys/mman.h>
#endif
#include <inttypes.h>
#include <string.h>
+
#include <memory>
-#include <string>
#define DEBUG_PARCEL 0
@@ -46,6 +45,8 @@ static jmethodID gBitmap_reinitMethodID;
namespace android {
+jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap);
+
class BitmapWrapper {
public:
explicit BitmapWrapper(Bitmap* bitmap)
@@ -368,15 +369,28 @@ static bool bitmapCopyTo(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src,
return srcPM.readPixels(dstPM);
}
-static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
- jint dstConfigHandle, jboolean isMutable) {
+static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle,
+ jboolean isMutable) {
+ LocalScopedBitmap bitmapHolder(srcHandle);
+ if (!bitmapHolder.valid()) {
+ return NULL;
+ }
+ const Bitmap& original = bitmapHolder->bitmap();
+ const bool hasGainmap = original.hasGainmap();
SkBitmap src;
- reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
+ bitmapHolder->getSkBitmap(&src);
+
if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) {
sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src));
if (!bitmap.get()) {
return NULL;
}
+ if (hasGainmap) {
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(original.gainmap());
+ if (gm) {
+ bitmap->setGainmap(std::move(gm));
+ }
+ }
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
@@ -388,6 +402,18 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
return NULL;
}
auto bitmap = allocator.getStorageObjAndReset();
+ if (hasGainmap) {
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ gainmap->info = original.gainmap()->info;
+ const SkBitmap skSrcBitmap = original.gainmap()->bitmap->getSkBitmap();
+ SkBitmap skDestBitmap;
+ HeapAllocator destAllocator;
+ if (!bitmapCopyTo(&skDestBitmap, dstCT, skSrcBitmap, &destAllocator)) {
+ return NULL;
+ }
+ gainmap->bitmap = sk_sp<Bitmap>(destAllocator.getStorageObjAndReset());
+ bitmap->setGainmap(std::move(gainmap));
+ }
return createBitmap(env, bitmap, getPremulBitmapCreateFlags(isMutable));
}
@@ -421,6 +447,11 @@ static jobject Bitmap_copyAshmemConfig(JNIEnv* env, jobject, jlong srcHandle, ji
return ret;
}
+static jint Bitmap_getAshmemFd(JNIEnv* env, jobject, jlong bitmapHandle) {
+ LocalScopedBitmap bitmap(bitmapHandle);
+ return (bitmap.valid()) ? bitmap->bitmap().getAshmemFd() : -1;
+}
+
static void Bitmap_destruct(BitmapWrapper* bitmap) {
delete bitmap;
}
@@ -576,91 +607,7 @@ static void Bitmap_setHasMipMap(JNIEnv* env, jobject, jlong bitmapHandle,
///////////////////////////////////////////////////////////////////////////////
// TODO: Move somewhere else
-#ifdef __ANDROID__ // Layoutlib does not support parcel
-
-class ScopedParcel {
-public:
- explicit ScopedParcel(JNIEnv* env, jobject parcel) {
- mParcel = AParcel_fromJavaParcel(env, parcel);
- }
-
- ~ScopedParcel() { AParcel_delete(mParcel); }
-
- int32_t readInt32() {
- int32_t temp = 0;
- // TODO: This behavior-matches what android::Parcel does
- // but this should probably be better
- if (AParcel_readInt32(mParcel, &temp) != STATUS_OK) {
- temp = 0;
- }
- return temp;
- }
-
- uint32_t readUint32() {
- uint32_t temp = 0;
- // TODO: This behavior-matches what android::Parcel does
- // but this should probably be better
- if (AParcel_readUint32(mParcel, &temp) != STATUS_OK) {
- temp = 0;
- }
- return temp;
- }
-
- void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
-
- void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
-
- bool allowFds() const { return AParcel_getAllowFds(mParcel); }
-
- std::optional<sk_sp<SkData>> readData() {
- struct Data {
- void* ptr = nullptr;
- size_t size = 0;
- } data;
- auto error = AParcel_readByteArray(mParcel, &data,
- [](void* arrayData, int32_t length,
- int8_t** outBuffer) -> bool {
- Data* data = reinterpret_cast<Data*>(arrayData);
- if (length > 0) {
- data->ptr = sk_malloc_canfail(length);
- if (!data->ptr) {
- return false;
- }
- *outBuffer =
- reinterpret_cast<int8_t*>(data->ptr);
- data->size = length;
- }
- return true;
- });
- if (error != STATUS_OK || data.size <= 0) {
- sk_free(data.ptr);
- return std::nullopt;
- } else {
- return SkData::MakeFromMalloc(data.ptr, data.size);
- }
- }
-
- void writeData(const std::optional<sk_sp<SkData>>& optData) {
- if (optData) {
- const auto& data = *optData;
- AParcel_writeByteArray(mParcel, reinterpret_cast<const int8_t*>(data->data()),
- data->size());
- } else {
- AParcel_writeByteArray(mParcel, nullptr, -1);
- }
- }
-
- AParcel* get() { return mParcel; }
-
-private:
- AParcel* mParcel;
-};
-
-enum class BlobType : int32_t {
- IN_PLACE,
- ASHMEM,
-};
-
+#ifdef __ANDROID__ // Layoutlib does not support parcel
#define ON_ERROR_RETURN(X) \
if ((error = (X)) != STATUS_OK) return error
@@ -1189,18 +1136,11 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false));
}
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
-typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
-AHB_from_HB AHardwareBuffer_fromHardwareBuffer;
-
-typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
-AHB_to_HB AHardwareBuffer_toHardwareBuffer;
-#endif
-
static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
jlong colorSpacePtr) {
#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
- AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
+ AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+ env, hardwareBuffer);
sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
@@ -1223,7 +1163,8 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) {
}
Bitmap& bitmap = bitmapHandle->bitmap();
- return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer());
+ return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+ env, bitmap.hardwareBuffer());
#else
return nullptr;
#endif
@@ -1251,67 +1192,85 @@ static void Bitmap_setImmutable(JNIEnv* env, jobject, jlong bitmapHandle) {
return bitmapHolder->bitmap().setImmutable();
}
+static jboolean Bitmap_hasGainmap(CRITICAL_JNI_PARAMS_COMMA jlong bitmapHandle) {
+ LocalScopedBitmap bitmapHolder(bitmapHandle);
+ if (!bitmapHolder.valid()) return false;
+
+ return bitmapHolder->bitmap().hasGainmap();
+}
+
+static jobject Bitmap_extractGainmap(JNIEnv* env, jobject, jlong bitmapHandle) {
+ LocalScopedBitmap bitmapHolder(bitmapHandle);
+ if (!bitmapHolder.valid()) return nullptr;
+ if (!bitmapHolder->bitmap().hasGainmap()) return nullptr;
+
+ return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap());
+}
+
+static void Bitmap_setGainmap(JNIEnv*, jobject, jlong bitmapHandle, jlong gainmapPtr) {
+ LocalScopedBitmap bitmapHolder(bitmapHandle);
+ if (!bitmapHolder.valid()) return;
+ uirenderer::Gainmap* gainmap = reinterpret_cast<uirenderer::Gainmap*>(gainmapPtr);
+ bitmapHolder->bitmap().setGainmap(sp<uirenderer::Gainmap>::fromExisting(gainmap));
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gBitmapMethods[] = {
- { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;",
- (void*)Bitmap_creator },
- { "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;",
- (void*)Bitmap_copy },
- { "nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;",
- (void*)Bitmap_copyAshmem },
- { "nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;",
- (void*)Bitmap_copyAshmemConfig },
- { "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
- { "nativeRecycle", "(J)V", (void*)Bitmap_recycle },
- { "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure },
- { "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
- (void*)Bitmap_compress },
- { "nativeErase", "(JI)V", (void*)Bitmap_erase },
- { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
- { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
- { "nativeConfig", "(J)I", (void*)Bitmap_config },
- { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
- { "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
- { "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
- { "nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied},
- { "nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap },
- { "nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap },
- { "nativeCreateFromParcel",
- "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
- (void*)Bitmap_createFromParcel },
- { "nativeWriteToParcel", "(JILandroid/os/Parcel;)Z",
- (void*)Bitmap_writeToParcel },
- { "nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;",
- (void*)Bitmap_extractAlpha },
- { "nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId },
- { "nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel },
- { "nativeGetColor", "(JII)J", (void*)Bitmap_getColor },
- { "nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels },
- { "nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel },
- { "nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels },
- { "nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V",
- (void*)Bitmap_copyPixelsToBuffer },
- { "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V",
- (void*)Bitmap_copyPixelsFromBuffer },
- { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs },
- { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw },
- { "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount },
- { "nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;",
- (void*)Bitmap_copyPreserveInternalConfig },
- { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
- (void*) Bitmap_wrapHardwareBufferBitmap },
- { "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
- (void*) Bitmap_getHardwareBuffer },
- { "nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace },
- { "nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace },
- { "nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB },
- { "nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
- { "nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
-
- // ------------ @CriticalNative ----------------
- { "nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
- { "nativeIsBackedByAshmem", "(J)Z", (void*)Bitmap_isBackedByAshmem}
+ {"nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", (void*)Bitmap_creator},
+ {"nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", (void*)Bitmap_copy},
+ {"nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmem},
+ {"nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmemConfig},
+ {"nativeGetAshmemFD", "(J)I", (void*)Bitmap_getAshmemFd},
+ {"nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer},
+ {"nativeRecycle", "(J)V", (void*)Bitmap_recycle},
+ {"nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure},
+ {"nativeCompress", "(JIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress},
+ {"nativeErase", "(JI)V", (void*)Bitmap_erase},
+ {"nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong},
+ {"nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes},
+ {"nativeConfig", "(J)I", (void*)Bitmap_config},
+ {"nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha},
+ {"nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
+ {"nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
+ {"nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied},
+ {"nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap},
+ {"nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap},
+ {"nativeCreateFromParcel", "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
+ (void*)Bitmap_createFromParcel},
+ {"nativeWriteToParcel", "(JILandroid/os/Parcel;)Z", (void*)Bitmap_writeToParcel},
+ {"nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;", (void*)Bitmap_extractAlpha},
+ {"nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId},
+ {"nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel},
+ {"nativeGetColor", "(JII)J", (void*)Bitmap_getColor},
+ {"nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels},
+ {"nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel},
+ {"nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels},
+ {"nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsToBuffer},
+ {"nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsFromBuffer},
+ {"nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs},
+ {"nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw},
+ {"nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount},
+ {"nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;",
+ (void*)Bitmap_copyPreserveInternalConfig},
+ {"nativeWrapHardwareBufferBitmap",
+ "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
+ (void*)Bitmap_wrapHardwareBufferBitmap},
+ {"nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
+ (void*)Bitmap_getHardwareBuffer},
+ {"nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;",
+ (void*)Bitmap_computeColorSpace},
+ {"nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace},
+ {"nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB},
+ {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
+ {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
+ {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap},
+ {"nativeSetGainmap", "(JJ)V", (void*)Bitmap_setGainmap},
+
+ // ------------ @CriticalNative ----------------
+ {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
+ {"nativeIsBackedByAshmem", "(J)Z", (void*)Bitmap_isBackedByAshmem},
+ {"nativeHasGainmap", "(J)Z", (void*)Bitmap_hasGainmap},
};
@@ -1321,18 +1280,7 @@ int register_android_graphics_Bitmap(JNIEnv* env)
gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
-
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- AHardwareBuffer_fromHardwareBuffer =
- (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
- LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr,
- "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
-
- AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
- LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr,
- " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
-#endif
+ uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
}
diff --git a/libs/hwui/jni/Bitmap.h b/libs/hwui/jni/Bitmap.h
index 73eca3aa8ef8..21a93f066d9b 100644
--- a/libs/hwui/jni/Bitmap.h
+++ b/libs/hwui/jni/Bitmap.h
@@ -19,7 +19,6 @@
#include <jni.h>
#include <android/bitmap.h>
-class SkBitmap;
struct SkImageInfo;
namespace android {
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 4e9daa4b0c16..c57e6f09347a 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -2,30 +2,43 @@
#define LOG_TAG "BitmapFactory"
#include "BitmapFactory.h"
+
+#include <Gainmap.h>
+#include <HardwareBitmapUploader.h>
+#include <androidfw/Asset.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <fcntl.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <memory>
+
#include "CreateJavaOutputStreamAdaptor.h"
#include "FrontBufferedStream.h"
#include "GraphicsJNI.h"
#include "MimeType.h"
#include "NinePatchPeeker.h"
#include "SkAndroidCodec.h"
+#include "SkBitmap.h"
+#include "SkBlendMode.h"
#include "SkCanvas.h"
-#include "SkMath.h"
+#include "SkColorSpace.h"
+#include "SkEncodedImageFormat.h"
+#include "SkGainmapInfo.h"
+#include "SkImageInfo.h"
+#include "SkPaint.h"
#include "SkPixelRef.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkSamplingOptions.h"
+#include "SkSize.h"
#include "SkStream.h"
#include "SkString.h"
-#include "SkUtils.h"
#include "Utils.h"
-#include <HardwareBitmapUploader.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <androidfw/Asset.h>
-#include <androidfw/ResourceTypes.h>
-#include <cutils/compiler.h>
-#include <fcntl.h>
-#include <memory>
-#include <stdio.h>
-#include <sys/stat.h>
-
jfieldID gOptions_justBoundsFieldID;
jfieldID gOptions_sampleSizeFieldID;
jfieldID gOptions_configFieldID;
@@ -132,7 +145,7 @@ public:
}
const size_t size = info.computeByteSize(bitmap->rowBytes());
- if (size > SK_MaxS32) {
+ if (size > INT32_MAX) {
ALOGW("bitmap is too large");
return false;
}
@@ -173,6 +186,115 @@ static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize,
needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
}
+static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+ sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
+ std::unique_ptr<SkAndroidCodec> codec;
+ codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
+ if (!codec) {
+ ALOGE("Can not create a codec for Gainmap.");
+ return false;
+ }
+ SkColorType decodeColorType = codec->computeOutputColorType(kN32_SkColorType);
+ sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(decodeColorType, nullptr);
+
+ SkISize size = codec->getSampledDimensions(sampleSize);
+
+ int scaledWidth = size.width();
+ int scaledHeight = size.height();
+ bool willScale = false;
+
+ // Apply a fine scaling step if necessary.
+ if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize) || scale != 1.0f) {
+ willScale = true;
+ // The operation below may loose precision (integer division), but it is put this way to
+ // mimic main image scale calculation
+ scaledWidth = static_cast<int>((codec->getInfo().width() / sampleSize) * scale + 0.5f);
+ scaledHeight = static_cast<int>((codec->getInfo().height() / sampleSize) * scale + 0.5f);
+ }
+
+ SkAlphaType alphaType = codec->computeOutputAlphaType(false);
+
+ const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
+ alphaType, decodeColorSpace);
+
+ const SkImageInfo& bitmapInfo = decodeInfo;
+ SkBitmap decodeBitmap;
+ sk_sp<Bitmap> nativeBitmap = nullptr;
+
+ if (!decodeBitmap.setInfo(bitmapInfo)) {
+ ALOGE("Failed to setInfo.");
+ return false;
+ }
+
+ if (willScale) {
+ if (!decodeBitmap.tryAllocPixels(nullptr)) {
+ ALOGE("OOM allocating gainmap pixels.");
+ return false;
+ }
+ } else {
+ nativeBitmap = android::Bitmap::allocateHeapBitmap(&decodeBitmap);
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating gainmap pixels.");
+ return false;
+ }
+ }
+
+ // Use SkAndroidCodec to perform the decode.
+ SkAndroidCodec::AndroidOptions codecOptions;
+ codecOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
+ codecOptions.fSampleSize = sampleSize;
+ SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodeBitmap.getPixels(),
+ decodeBitmap.rowBytes(), &codecOptions);
+ switch (result) {
+ case SkCodec::kSuccess:
+ case SkCodec::kIncompleteInput:
+ break;
+ default:
+ ALOGE("Error decoding gainmap.");
+ return false;
+ }
+
+ if (willScale) {
+ SkBitmap gainmapBitmap;
+ const float scaleX = scaledWidth / float(decodeBitmap.width());
+ const float scaleY = scaledHeight / float(decodeBitmap.height());
+
+ SkColorType scaledColorType = decodeBitmap.colorType();
+ gainmapBitmap.setInfo(
+ bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
+
+ nativeBitmap = android::Bitmap::allocateHeapBitmap(&gainmapBitmap);
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating gainmap pixels.");
+ return false;
+ }
+
+ SkPaint paint;
+ // kSrc_Mode instructs us to overwrite the uninitialized pixels in
+ // outputBitmap. Otherwise we would blend by default, which is not
+ // what we want.
+ paint.setBlendMode(SkBlendMode::kSrc);
+
+ SkCanvas canvas(gainmapBitmap, SkCanvas::ColorBehavior::kLegacy);
+ canvas.scale(scaleX, scaleY);
+ decodeBitmap.setImmutable(); // so .asImage() doesn't make a copy
+ canvas.drawImage(decodeBitmap.asImage(), 0.0f, 0.0f,
+ SkSamplingOptions(SkFilterMode::kLinear), &paint);
+ }
+
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ if (!gainmap) {
+ ALOGE("OOM allocating Gainmap");
+ return false;
+ }
+
+ gainmap->info = gainmapInfo;
+ gainmap->bitmap = std::move(nativeBitmap);
+ *outGainmap = std::move(gainmap);
+
+ return true;
+}
+
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle) {
@@ -476,6 +598,19 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
return nullObjectReturn("Got null SkPixelRef");
}
+ bool hasGainmap = false;
+ SkGainmapInfo gainmapInfo;
+ std::unique_ptr<SkStream> gainmapStream = nullptr;
+ sp<uirenderer::Gainmap> gainmap = nullptr;
+ if (result == SkCodec::kSuccess) {
+ hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+ }
+
+ if (hasGainmap) {
+ hasGainmap =
+ decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+ }
+
if (!isMutable && javaBitmap == NULL) {
// promise we will never change our pixels (great for sharing and pictures)
outputBitmap.setImmutable();
@@ -483,6 +618,9 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
bool isPremultiplied = !requireUnpremultiplied;
if (javaBitmap != nullptr) {
+ if (hasGainmap) {
+ reuseBitmap->setGainmap(std::move(gainmap));
+ }
bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
outputBitmap.notifyPixelsChanged();
// If a java bitmap was passed in for reuse, pass it back
@@ -498,13 +636,25 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
if (!hardwareBitmap.get()) {
return nullObjectReturn("Failed to allocate a hardware bitmap");
}
+ if (hasGainmap) {
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
+ }
+
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets, -1);
}
+ Bitmap* heapBitmap = defaultAllocator.getStorageObjAndReset();
+ if (hasGainmap && heapBitmap != nullptr) {
+ heapBitmap->setGainmap(std::move(gainmap));
+ }
+
// now create the java bitmap
- return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
- bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
+ return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
+ -1);
}
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 1c20415dcc8f..aeaa17198be5 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -17,27 +17,139 @@
#undef LOG_TAG
#define LOG_TAG "BitmapRegionDecoder"
+#include "BitmapRegionDecoder.h"
+
+#include <HardwareBitmapUploader.h>
+#include <androidfw/Asset.h>
+#include <sys/stat.h>
+
+#include <memory>
+
#include "BitmapFactory.h"
#include "CreateJavaOutputStreamAdaptor.h"
+#include "Gainmap.h"
#include "GraphicsJNI.h"
-#include "Utils.h"
-
-#include "BitmapRegionDecoder.h"
#include "SkBitmap.h"
#include "SkCodec.h"
+#include "SkColorSpace.h"
#include "SkData.h"
+#include "SkGainmapInfo.h"
#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "Utils.h"
-#include <HardwareBitmapUploader.h>
-#include <androidfw/Asset.h>
-#include <sys/stat.h>
+using namespace android;
-#include <memory>
+namespace android {
+class BitmapRegionDecoderWrapper {
+public:
+ static std::unique_ptr<BitmapRegionDecoderWrapper> Make(sk_sp<SkData> data) {
+ std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD =
+ skia::BitmapRegionDecoder::Make(std::move(data));
+ if (!mainImageBRD) {
+ return nullptr;
+ }
-using namespace android;
+ SkGainmapInfo gainmapInfo;
+ std::unique_ptr<SkStream> gainmapStream;
+ std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
+ if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+ sk_sp<SkData> data = nullptr;
+ if (gainmapStream->getMemoryBase()) {
+ // It is safe to make without copy because we'll hold onto the stream.
+ data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(),
+ gainmapStream->getLength());
+ } else {
+ data = SkCopyStreamToData(gainmapStream.get());
+ // We don't need to hold the stream anymore
+ gainmapStream = nullptr;
+ }
+ gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data));
+ }
+
+ return std::unique_ptr<BitmapRegionDecoderWrapper>(
+ new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD),
+ gainmapInfo, std::move(gainmapStream)));
+ }
+
+ SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
+
+ SkColorType computeOutputColorType(SkColorType requestedColorType) {
+ return mMainImageBRD->computeOutputColorType(requestedColorType);
+ }
+
+ sk_sp<SkColorSpace> computeOutputColorSpace(SkColorType outputColorType,
+ sk_sp<SkColorSpace> prefColorSpace = nullptr) {
+ return mMainImageBRD->computeOutputColorSpace(outputColorType, prefColorSpace);
+ }
+
+ bool decodeRegion(SkBitmap* bitmap, skia::BRDAllocator* allocator, const SkIRect& desiredSubset,
+ int sampleSize, SkColorType colorType, bool requireUnpremul,
+ sk_sp<SkColorSpace> prefColorSpace) {
+ return mMainImageBRD->decodeRegion(bitmap, allocator, desiredSubset, sampleSize, colorType,
+ requireUnpremul, prefColorSpace);
+ }
+
+ bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset,
+ int sampleSize, bool requireUnpremul) {
+ SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
+ sk_sp<SkColorSpace> decodeColorSpace =
+ mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
+ SkBitmap bm;
+ HeapAllocator heapAlloc;
+ if (!mGainmapBRD->decodeRegion(&bm, &heapAlloc, desiredSubset, sampleSize, decodeColorType,
+ requireUnpremul, decodeColorSpace)) {
+ ALOGE("Error decoding Gainmap region");
+ return false;
+ }
+ sk_sp<Bitmap> nativeBitmap(heapAlloc.getStorageObjAndReset());
+ if (!nativeBitmap) {
+ ALOGE("OOM allocating Bitmap for Gainmap");
+ return false;
+ }
+ auto gainmap = sp<uirenderer::Gainmap>::make();
+ if (!gainmap) {
+ ALOGE("OOM allocating Gainmap");
+ return false;
+ }
+ gainmap->info = mGainmapInfo;
+ gainmap->bitmap = std::move(nativeBitmap);
+ *outGainmap = std::move(gainmap);
+ return true;
+ }
+
+ SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) {
+ const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
+ const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
+ // TODO: Account for rounding error?
+ return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
+ mainImageRegion.right() * scaleX,
+ mainImageRegion.bottom() * scaleY);
+ }
+
+ bool hasGainmap() { return mGainmapBRD != nullptr; }
+
+ int width() const { return mMainImageBRD->width(); }
+ int height() const { return mMainImageBRD->height(); }
+
+private:
+ BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
+ std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
+ SkGainmapInfo info, std::unique_ptr<SkStream> stream)
+ : mMainImageBRD(std::move(mainImageBRD))
+ , mGainmapBRD(std::move(gainmapBRD))
+ , mGainmapInfo(info)
+ , mGainmapStream(std::move(stream)) {}
+
+ std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
+ std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
+ SkGainmapInfo mGainmapInfo;
+ std::unique_ptr<SkStream> mGainmapStream;
+};
+} // namespace android
static jobject createBitmapRegionDecoder(JNIEnv* env, sk_sp<SkData> data) {
- auto brd = skia::BitmapRegionDecoder::Make(std::move(data));
+ auto brd = android::BitmapRegionDecoderWrapper::Make(std::move(data));
if (!brd) {
doThrowIOE(env, "Image format not supported");
return nullObjectReturn("CreateBitmapRegionDecoder returned null");
@@ -135,7 +247,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
recycledBytes = recycledBitmap->getAllocationByteCount();
}
- auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
+ auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
SkColorType decodeColorType = brd->computeOutputColorType(colorType);
if (isHardware) {
@@ -195,9 +307,22 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
GraphicsJNI::getColorSpace(env, decodeColorSpace.get(), decodeColorType));
}
+ sp<uirenderer::Gainmap> gainmap;
+ bool hasGainmap = brd->hasGainmap();
+ if (hasGainmap) {
+ SkIRect gainmapSubset = brd->calculateGainmapRegion(subset);
+ if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) {
+ // If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
+ hasGainmap = false;
+ }
+ }
+
// If we may have reused a bitmap, we need to indicate that the pixels have changed.
if (javaBitmap) {
recycleAlloc.copyIfNecessary();
+ if (hasGainmap) {
+ recycledBitmap->setGainmap(std::move(gainmap));
+ }
bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
return javaBitmap;
}
@@ -208,23 +333,33 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
}
if (isHardware) {
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
+ if (hasGainmap) {
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
+ if (gm) {
+ hardwareBitmap->setGainmap(std::move(gm));
+ }
+ }
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
- return android::bitmap::createBitmap(env, heapAlloc.getStorageObjAndReset(), bitmapCreateFlags);
+ Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
+ if (hasGainmap && heapBitmap != nullptr) {
+ heapBitmap->setGainmap(std::move(gainmap));
+ }
+ return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
}
static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
- auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
+ auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
return static_cast<jint>(brd->height());
}
static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) {
- auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
+ auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
return static_cast<jint>(brd->width());
}
static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) {
- auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
+ auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
delete brd;
}
diff --git a/libs/hwui/jni/BufferUtils.cpp b/libs/hwui/jni/BufferUtils.cpp
new file mode 100644
index 000000000000..3eb08d7552da
--- /dev/null
+++ b/libs/hwui/jni/BufferUtils.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "BufferUtils.h"
+
+#include "graphics_jni_helpers.h"
+
+static void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+ if (src) {
+ dst.resize(srcSize);
+ memcpy(dst.data(), src, srcSize);
+ }
+}
+
+/**
+ * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+ * from a java.nio.Buffer.
+ */
+static void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ if (pointer == 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Must use a native order direct Buffer");
+ return nullptr;
+ }
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+}
+
+static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+ env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+}
+
+static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, jint* offset) {
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ *remaining = (limit - position) << elementSizeShift;
+ if (pointer != 0L) {
+ *array = nullptr;
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ *array = jniGetNioBufferBaseArray(env, buffer);
+ *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+ return nullptr;
+}
+
+/**
+ * This is a copy of
+ * static void android_glBufferData__IILjava_nio_Buffer_2I
+ * from com_google_android_gles_jni_GLImpl.cpp
+ */
+static void setIndirectData(JNIEnv* env, size_t size, jobject data_buf,
+ std::vector<uint8_t>& result) {
+ jint exception = 0;
+ const char* exceptionType = nullptr;
+ const char* exceptionMessage = nullptr;
+ jarray array = nullptr;
+ jint bufferOffset = 0;
+ jint remaining;
+ void* data = 0;
+ char* dataBase = nullptr;
+
+ if (data_buf) {
+ data = getPointer(env, data_buf, (jarray*)&array, &remaining, &bufferOffset);
+ if (remaining < size) {
+ exception = 1;
+ exceptionType = "java/lang/IllegalArgumentException";
+ exceptionMessage = "remaining() < size < needed";
+ goto exit;
+ }
+ }
+ if (data_buf && data == nullptr) {
+ dataBase = (char*)env->GetPrimitiveArrayCritical(array, (jboolean*)0);
+ data = (void*)(dataBase + bufferOffset);
+ }
+
+ copyToVector(result, data, size);
+
+exit:
+ if (array) {
+ releasePointer(env, array, (void*)dataBase, JNI_FALSE);
+ }
+ if (exception) {
+ jniThrowException(env, exceptionType, exceptionMessage);
+ }
+}
+
+std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
+ jboolean isDirect) {
+ std::vector<uint8_t> data;
+ if (buffer == nullptr) {
+ jniThrowNullPointerException(env);
+ } else {
+ if (isDirect) {
+ void* directBufferPtr = getDirectBufferPointer(env, buffer);
+ if (directBufferPtr) {
+ copyToVector(data, directBufferPtr, size);
+ }
+ } else {
+ setIndirectData(env, size, buffer, data);
+ }
+ }
+ return data;
+}
diff --git a/libs/hwui/jni/BufferUtils.h b/libs/hwui/jni/BufferUtils.h
new file mode 100644
index 000000000000..b43c320b7771
--- /dev/null
+++ b/libs/hwui/jni/BufferUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef BUFFERUTILS_H_
+#define BUFFERUTILS_H_
+
+#include <jni.h>
+
+#include <vector>
+
+/**
+ * Helper method to load a java.nio.Buffer instance into a vector. This handles
+ * both direct and indirect buffers and promptly releases any critical arrays that
+ * have been retrieved in order to avoid potential jni exceptions due to interleaved
+ * jni calls between get/release primitive method invocations.
+ */
+std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
+ jboolean isDirect);
+
+#endif // BUFFERUTILS_H_
diff --git a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp
index b10540cb3fbd..97dbc9ac171f 100644
--- a/libs/hwui/jni/ByteBufferStreamAdaptor.cpp
+++ b/libs/hwui/jni/ByteBufferStreamAdaptor.cpp
@@ -2,6 +2,7 @@
#include "GraphicsJNI.h"
#include "Utils.h"
+#include <SkData.h>
#include <SkStream.h>
using namespace android;
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index cef21f91f3c1..4bd7ef47b871 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -17,6 +17,7 @@
#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
#include "SkColorFilter.h"
#include "SkColorMatrixFilter.h"
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index ce5ac382aeff..28e71d74e5b9 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -24,6 +24,7 @@
#include "SkData.h"
#include "SkFontMgr.h"
#include "SkRefCnt.h"
+#include "SkStream.h"
#include "SkTypeface.h"
#include "Utils.h"
#include "fonts/Font.h"
@@ -84,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) {
if (builder->fonts.empty()) {
return 0;
}
- std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
+ std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
builder->langId, builder->variant, std::move(builder->fonts),
- true /* isCustomFallback */);
+ true /* isCustomFallback */, false /* isDefaultFallback */);
if (family->getCoverage().length() == 0) {
return 0;
}
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index fef51b8d2f79..ae6ac4ce4ecc 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -7,9 +7,11 @@
#include "Movie.h"
+#include "SkBitmap.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkStream.h"
+#include "SkTypes.h"
#include "gif_lib.h"
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
new file mode 100644
index 000000000000..0f8a85dd9e62
--- /dev/null
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <Gainmap.h>
+
+#ifdef __ANDROID__
+#include <binder/Parcel.h>
+#endif
+
+#include "Bitmap.h"
+#include "GraphicsJNI.h"
+#include "ScopedParcel.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+static jclass gGainmap_class;
+static jmethodID gGainmap_constructorMethodID;
+
+using namespace uirenderer;
+
+static Gainmap* fromJava(jlong gainmap) {
+ return reinterpret_cast<Gainmap*>(gainmap);
+}
+
+static int getCreateFlags(const sk_sp<Bitmap>& bitmap) {
+ int flags = 0;
+ if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
+ flags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
+ }
+ if (!bitmap->isImmutable()) {
+ flags |= android::bitmap::kBitmapCreateFlag_Mutable;
+ }
+ return flags;
+}
+
+jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) {
+ auto gainmap = bitmap.gainmap();
+ jobject jGainmapImage;
+
+ {
+ // Scope to guard the release of nativeBitmap
+ auto nativeBitmap = gainmap->bitmap;
+ const int createFlags = getCreateFlags(nativeBitmap);
+ jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags);
+ }
+
+ // Grab a ref for the jobject
+ gainmap->incStrong(0);
+ jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage,
+ gainmap.get());
+
+ if (env->ExceptionCheck() != 0) {
+ // sadtrombone
+ gainmap->decStrong(0);
+ ALOGE("*** Uncaught exception returned from Java call!\n");
+ env->ExceptionDescribe();
+ }
+ return obj;
+}
+
+static void Gainmap_destructor(Gainmap* gainmap) {
+ gainmap->decStrong(0);
+}
+
+static jlong Gainmap_getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Gainmap_destructor));
+}
+
+jlong Gainmap_createEmpty(JNIEnv*, jobject) {
+ Gainmap* gainmap = new Gainmap();
+ gainmap->incStrong(0);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
+}
+
+static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
+ android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
+ fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
+}
+
+static void Gainmap_setRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapRatioMin = {r, g, b, 1.f};
+}
+
+static void Gainmap_getRatioMin(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMin;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapRatioMax = {r, g, b, 1.f};
+}
+
+static void Gainmap_getRatioMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMax;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setGamma(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+ fromJava(gainmapPtr)->info.fGainmapGamma = {r, g, b, 1.f};
+}
+
+static void Gainmap_getGamma(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fGainmapGamma;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+ jfloat b) {
+ fromJava(gainmapPtr)->info.fEpsilonSdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonSdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fEpsilonSdr;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+ jfloat b) {
+ fromJava(gainmapPtr)->info.fEpsilonHdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonHdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+ const auto value = fromJava(gainmapPtr)->info.fEpsilonHdr;
+ jfloat buf[3]{value.fR, value.fG, value.fB};
+ env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
+ fromJava(gainmapPtr)->info.fDisplayRatioHdr = max;
+}
+
+static jfloat Gainmap_getDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr) {
+ return fromJava(gainmapPtr)->info.fDisplayRatioHdr;
+}
+
+static void Gainmap_setDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
+ fromJava(gainmapPtr)->info.fDisplayRatioSdr = min;
+}
+
+static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) {
+ return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
+}
+
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
+#ifdef __ANDROID__ // Layoutlib does not support parcel
+ if (parcel == NULL) {
+ ALOGD("write null parcel\n");
+ return;
+ }
+ ScopedParcel p(env, parcel);
+ SkGainmapInfo info = fromJava(nativeObject)->info;
+ // write gainmap to parcel
+ // ratio min
+ p.writeFloat(info.fGainmapRatioMin.fR);
+ p.writeFloat(info.fGainmapRatioMin.fG);
+ p.writeFloat(info.fGainmapRatioMin.fB);
+ // ratio max
+ p.writeFloat(info.fGainmapRatioMax.fR);
+ p.writeFloat(info.fGainmapRatioMax.fG);
+ p.writeFloat(info.fGainmapRatioMax.fB);
+ // gamma
+ p.writeFloat(info.fGainmapGamma.fR);
+ p.writeFloat(info.fGainmapGamma.fG);
+ p.writeFloat(info.fGainmapGamma.fB);
+ // epsilonsdr
+ p.writeFloat(info.fEpsilonSdr.fR);
+ p.writeFloat(info.fEpsilonSdr.fG);
+ p.writeFloat(info.fEpsilonSdr.fB);
+ // epsilonhdr
+ p.writeFloat(info.fEpsilonHdr.fR);
+ p.writeFloat(info.fEpsilonHdr.fG);
+ p.writeFloat(info.fEpsilonHdr.fB);
+ // display ratio sdr
+ p.writeFloat(info.fDisplayRatioSdr);
+ // display ratio hdr
+ p.writeFloat(info.fDisplayRatioHdr);
+ // base image type
+ p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
+ // type
+ p.writeInt32(static_cast<int32_t>(info.fType));
+#else
+ doThrowRE(env, "Cannot use parcels outside of Android!");
+#endif
+}
+
+static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
+#ifdef __ANDROID__ // Layoutlib does not support parcel
+ if (parcel == NULL) {
+ jniThrowNullPointerException(env, "parcel cannot be null");
+ return;
+ }
+ ScopedParcel p(env, parcel);
+
+ SkGainmapInfo info;
+ info.fGainmapRatioMin = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+ info.fGainmapRatioMax = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+ info.fGainmapGamma = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+ info.fEpsilonSdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+ info.fEpsilonHdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
+ info.fDisplayRatioSdr = p.readFloat();
+ info.fDisplayRatioHdr = p.readFloat();
+ info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
+ info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
+
+ fromJava(nativeObject)->info = info;
+#else
+ jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gGainmapMethods[] = {
+ {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
+ {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+ {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
+ {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
+ {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
+ {"nSetRatioMax", "(JFFF)V", (void*)Gainmap_setRatioMax},
+ {"nGetRatioMax", "(J[F)V", (void*)Gainmap_getRatioMax},
+ {"nSetGamma", "(JFFF)V", (void*)Gainmap_setGamma},
+ {"nGetGamma", "(J[F)V", (void*)Gainmap_getGamma},
+ {"nSetEpsilonSdr", "(JFFF)V", (void*)Gainmap_setEpsilonSdr},
+ {"nGetEpsilonSdr", "(J[F)V", (void*)Gainmap_getEpsilonSdr},
+ {"nSetEpsilonHdr", "(JFFF)V", (void*)Gainmap_setEpsilonHdr},
+ {"nGetEpsilonHdr", "(J[F)V", (void*)Gainmap_getEpsilonHdr},
+ {"nSetDisplayRatioHdr", "(JF)V", (void*)Gainmap_setDisplayRatioHdr},
+ {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
+ {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
+ {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
+ {"nWriteGainmapToParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_writeToParcel},
+ {"nReadGainmapFromParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_readFromParcel},
+};
+
+int register_android_graphics_Gainmap(JNIEnv* env) {
+ gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap"));
+ gGainmap_constructorMethodID =
+ GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;J)V");
+ return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods,
+ NELEM(gGainmapMethods));
+}
+
+} // namespace android
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 33669ac0a34e..28d78b4474f7 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -8,12 +8,19 @@
#include <nativehelper/JNIHelp.h>
#include "GraphicsJNI.h"
+#include "SkBitmap.h"
#include "SkCanvas.h"
+#include "SkColorSpace.h"
#include "SkFontMetrics.h"
-#include "SkMath.h"
+#include "SkImageInfo.h"
+#include "SkPixelRef.h"
+#include "SkPoint.h"
+#include "SkRect.h"
#include "SkRegion.h"
+#include "SkTypes.h"
#include <cutils/ashmem.h>
#include <hwui/Canvas.h>
+#include <log/log.h>
using namespace android;
@@ -483,7 +490,7 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
void GraphicsJNI::set_metrics(JNIEnv* env, jobject metrics, const SkFontMetrics& skmetrics) {
if (metrics == nullptr) return;
- SkASSERT(env->IsInstanceOf(metrics, gFontMetrics_class));
+ LOG_FATAL_IF(!env->IsInstanceOf(metrics, gFontMetrics_class));
env->SetFloatField(metrics, gFontMetrics_top, SkScalarToFloat(skmetrics.fTop));
env->SetFloatField(metrics, gFontMetrics_ascent, SkScalarToFloat(skmetrics.fAscent));
env->SetFloatField(metrics, gFontMetrics_descent, SkScalarToFloat(skmetrics.fDescent));
@@ -497,7 +504,7 @@ int GraphicsJNI::set_metrics_int(JNIEnv* env, jobject metrics, const SkFontMetri
int leading = SkScalarRoundToInt(skmetrics.fLeading);
if (metrics) {
- SkASSERT(env->IsInstanceOf(metrics, gFontMetricsInt_class));
+ LOG_FATAL_IF(!env->IsInstanceOf(metrics, gFontMetricsInt_class));
env->SetIntField(metrics, gFontMetricsInt_top, SkScalarFloorToInt(skmetrics.fTop));
env->SetIntField(metrics, gFontMetricsInt_ascent, ascent);
env->SetIntField(metrics, gFontMetricsInt_descent, descent);
@@ -509,8 +516,7 @@ int GraphicsJNI::set_metrics_int(JNIEnv* env, jobject metrics, const SkFontMetri
///////////////////////////////////////////////////////////////////////////////////////////
-jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, skia::BitmapRegionDecoder* bitmap)
-{
+jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoderWrapper* bitmap) {
ALOG_ASSERT(bitmap != NULL);
jobject obj = env->NewObject(gBitmapRegionDecoder_class,
@@ -568,14 +574,22 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace,
LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix));
skcms_TransferFunction transferParams;
- // We can only handle numerical transfer functions at the moment
- LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams));
-
- jobject params = env->NewObject(gTransferParameters_class,
- gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g);
+ decodeColorSpace->transferFn(&transferParams);
+ auto res = skcms_TransferFunction_getType(&transferParams);
+ LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
+
+ jobject params;
+ if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, true);
+ } else {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, false);
+ }
jfloatArray xyzArray = env->NewFloatArray(9);
jfloat xyz[9] = {
@@ -698,7 +712,9 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() {
mSkiaBitmap->info().height());
for (int y = 0; y < rowsToCopy; y++) {
memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
- dst = SkTAddOffset<void>(dst, dstRowBytes);
+ // Cast to bytes in order to apply the dstRowBytes offset correctly.
+ dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
}
recycledPixels->notifyPixelsChanged();
recycledPixels->unref();
@@ -800,8 +816,8 @@ int register_android_graphics_Graphics(JNIEnv* env)
gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/ColorSpace$Rgb$TransferParameters"));
- gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
- "<init>", "(DDDDDDD)V");
+ gTransferParameters_constructorMethodID =
+ GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 085a905abaf8..24f9e82b5340 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -2,28 +2,25 @@
#define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_
#include <cutils/compiler.h>
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
-#include "Bitmap.h"
#include "BRDAllocator.h"
+#include "Bitmap.h"
#include "SkBitmap.h"
#include "SkCodec.h"
-#include "SkPixelRef.h"
+#include "SkColorSpace.h"
#include "SkMallocPixelRef.h"
+#include "SkPixelRef.h"
#include "SkPoint.h"
#include "SkRect.h"
-#include "SkColorSpace.h"
-#include <hwui/Canvas.h>
-#include <hwui/Bitmap.h>
-
#include "graphics_jni_helpers.h"
class SkCanvas;
struct SkFontMetrics;
namespace android {
-namespace skia {
- class BitmapRegionDecoder;
-}
+class BitmapRegionDecoderWrapper;
class Canvas;
class Paint;
struct Typeface;
@@ -49,10 +46,16 @@ public:
static void setJavaVM(JavaVM* javaVM);
- /** returns a pointer to the JavaVM provided when we initialized the module */
+ /**
+ * returns a pointer to the JavaVM provided when we initialized the module
+ * DEPRECATED: Objects should know the JavaVM that created them
+ */
static JavaVM* getJavaVM() { return mJavaVM; }
- /** return a pointer to the JNIEnv for this thread */
+ /**
+ * return a pointer to the JNIEnv for this thread
+ * DEPRECATED: Objects should know the JavaVM that created them
+ */
static JNIEnv* getJNIEnv();
/** create a JNIEnv* for this thread or assert if one already exists */
@@ -120,7 +123,7 @@ public:
static jobject createRegion(JNIEnv* env, SkRegion* region);
static jobject createBitmapRegionDecoder(JNIEnv* env,
- android::skia::BitmapRegionDecoder* bitmap);
+ android::BitmapRegionDecoderWrapper* bitmap);
/**
* Given a bitmap we natively allocate a memory block to store the contents
@@ -335,6 +338,34 @@ private:
int fLen;
};
+class JGlobalRefHolder {
+public:
+ JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
+
+ virtual ~JGlobalRefHolder() {
+ env()->DeleteGlobalRef(mObject);
+ mObject = nullptr;
+ }
+
+ jobject object() { return mObject; }
+ JavaVM* vm() { return mVm; }
+
+ JNIEnv* env() {
+ JNIEnv* env;
+ if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+ }
+ return env;
+ }
+
+private:
+ JGlobalRefHolder(const JGlobalRefHolder&) = delete;
+ void operator=(const JGlobalRefHolder&) = delete;
+
+ JavaVM* mVm;
+ jobject mObject;
+};
+
void doThrowNPE(JNIEnv* env);
void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception
void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
new file mode 100644
index 000000000000..7e3f771b6b3d
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HardwareBufferHelpers.h"
+
+#include <dlfcn.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
+typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
+static AHB_from_HB fromHardwareBuffer = nullptr;
+static AHB_to_HB toHardwareBuffer = nullptr;
+#endif
+
+void android::uirenderer::HardwareBufferHelpers::init() {
+#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
+ LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+ "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
+
+ toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
+ LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+ " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
+#endif
+}
+
+AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+ JNIEnv* env, jobject hardwarebuffer) {
+#ifdef __ANDROID__
+ LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+ "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget "
+ "to call HardwareBufferHelpers::init?");
+ return fromHardwareBuffer(env, hardwarebuffer);
+#else
+ ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android "
+ "configuration");
+ return nullptr;
+#endif
+}
+
+jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* ahardwarebuffer) {
+#ifdef __ANDROID__
+ LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+ "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to "
+ "call HardwareBufferHelpers::init?");
+ return toHardwareBuffer(env, ahardwarebuffer);
+#else
+ ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android "
+ "configuration");
+ return nullptr;
+#endif
+} \ No newline at end of file
diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h
new file mode 100644
index 000000000000..326babfb0b34
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#ifndef HARDWAREBUFFER_JNI_HELPERS_H
+#define HARDWAREBUFFER_JNI_HELPERS_H
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+namespace android {
+namespace uirenderer {
+
+class HardwareBufferHelpers {
+public:
+ static void init();
+ static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject);
+ static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*);
+
+private:
+ HardwareBufferHelpers() = default; // not to be instantiated
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // HARDWAREBUFFER_JNI_HELPERS_H \ No newline at end of file
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index f7b8c014be6e..db1c188e425e 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -14,28 +14,38 @@
* limitations under the License.
*/
-#include "Bitmap.h"
-#include "BitmapFactory.h"
-#include "ByteBufferStreamAdaptor.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include "GraphicsJNI.h"
#include "ImageDecoder.h"
-#include "NinePatchPeeker.h"
-#include "Utils.h"
-
-#include <hwui/Bitmap.h>
-#include <hwui/ImageDecoder.h>
-#include <HardwareBitmapUploader.h>
#include <FrontBufferedStream.h>
+#include <HardwareBitmapUploader.h>
+#include <SkAlphaType.h>
#include <SkAndroidCodec.h>
-#include <SkEncodedImageFormat.h>
+#include <SkBitmap.h>
+#include <SkCodec.h>
+#include <SkCodecAnimation.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <SkImageInfo.h>
+#include <SkRect.h>
+#include <SkSize.h>
#include <SkStream.h>
-
+#include <SkString.h>
#include <androidfw/Asset.h>
#include <fcntl.h>
+#include <gui/TraceUtils.h>
+#include <hwui/Bitmap.h>
+#include <hwui/ImageDecoder.h>
#include <sys/stat.h>
+#include "Bitmap.h"
+#include "BitmapFactory.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "Gainmap.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+
using namespace android;
static jclass gImageDecoder_class;
@@ -242,6 +252,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
jboolean requireUnpremul, jboolean preferRamOverQuality,
jboolean asAlphaMask, jlong colorSpaceHandle,
jboolean extended) {
+ ATRACE_CALL();
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
if (!decoder->setTargetSize(targetWidth, targetHeight)) {
doThrowISE(env, "Could not scale to target size!");
@@ -332,10 +343,22 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
return nullptr;
}
+ ATRACE_FORMAT("Decoding %dx%d bitmap", bitmapInfo.width(), bitmapInfo.height());
SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes());
jthrowable jexception = get_and_clear_exception(env);
- int onPartialImageError = jexception ? kSourceException
- : 0; // No error.
+ int onPartialImageError = jexception ? kSourceException : 0; // No error.
+
+ // Only attempt to extract the gainmap if we're not post-processing, as we can't automatically
+ // mimic that to the gainmap and expect it to be meaningful. And also don't extract the gainmap
+ // if we're prioritizing RAM over quality, since the gainmap improves quality at the
+ // cost of RAM
+ if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
+ // The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
+ result = decoder->extractGainmap(nativeBitmap.get(),
+ allocator == kSharedMemory_Allocator ? true : false);
+ jexception = get_and_clear_exception(env);
+ }
+
switch (result) {
case SkCodec::kSuccess:
// Ignore the exception, since the decode was successful anyway.
@@ -446,6 +469,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
if (hwBitmap) {
hwBitmap->setImmutable();
+ if (nativeBitmap->hasGainmap()) {
+ auto gm = uirenderer::Gainmap::allocateHardwareGainmap(nativeBitmap->gainmap());
+ if (gm) {
+ hwBitmap->setGainmap(std::move(gm));
+ }
+ }
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
}
diff --git a/libs/hwui/jni/Interpolator.cpp b/libs/hwui/jni/Interpolator.cpp
index fc3d70b87f5a..c71e3085caf5 100644
--- a/libs/hwui/jni/Interpolator.cpp
+++ b/libs/hwui/jni/Interpolator.cpp
@@ -24,12 +24,8 @@ static void Interpolator_setKeyFrame(JNIEnv* env, jobject clazz, jlong interpHan
AutoJavaFloatArray autoValues(env, valueArray);
AutoJavaFloatArray autoBlend(env, blendArray, 4);
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* scalars = autoValues.ptr();
SkScalar* blend = autoBlend.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
interp->setKeyFrame(index, msec, scalars, blend);
}
diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h
new file mode 100644
index 000000000000..3a3587572a1a
--- /dev/null
+++ b/libs/hwui/jni/JvmErrorReporter.h
@@ -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.
+ */
+#ifndef JVMERRORREPORTER_H
+#define JVMERRORREPORTER_H
+
+#include <TreeInfo.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "GraphicsJNI.h"
+
+namespace android {
+namespace uirenderer {
+
+class JvmErrorReporter : public android::uirenderer::ErrorHandler {
+public:
+ JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); }
+
+ virtual void onError(const std::string& message) override {
+ JNIEnv* env;
+ if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
+ }
+ jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
+ }
+
+private:
+ JavaVM* mVm;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // JVMERRORREPORTER_H
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 5383032e0f77..048ce025ce27 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -2,6 +2,7 @@
#include "SkMaskFilter.h"
#include "SkBlurMask.h"
#include "SkBlurMaskFilter.h"
+#include "SkBlurTypes.h"
#include "SkTableMaskFilter.h"
static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
new file mode 100644
index 000000000000..ae9792df3d82
--- /dev/null
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+using Attribute = SkMeshSpecification::Attribute;
+using Varying = SkMeshSpecification::Varying;
+
+static struct {
+ jclass clazz{};
+ jfieldID type{};
+ jfieldID offset{};
+ jfieldID name{};
+} gAttributeInfo;
+
+static struct {
+ jclass clazz{};
+ jfieldID type{};
+ jfieldID name{};
+} gVaryingInfo;
+
+std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) {
+ int size = env->GetArrayLength(attributes);
+ std::vector<Attribute> attVector;
+ attVector.reserve(size);
+ for (int i = 0; i < size; i++) {
+ jobject attribute = env->GetObjectArrayElement(attributes, i);
+ auto name = (jstring)env->GetObjectField(attribute, gAttributeInfo.name);
+ auto attName = ScopedUtfChars(env, name);
+ Attribute temp{Attribute::Type(env->GetIntField(attribute, gAttributeInfo.type)),
+ static_cast<size_t>(env->GetIntField(attribute, gAttributeInfo.offset)),
+ SkString(attName.c_str())};
+ attVector.push_back(std::move(temp));
+ }
+ return attVector;
+}
+
+std::vector<Varying> extractVaryings(JNIEnv* env, jobjectArray varyings) {
+ int size = env->GetArrayLength(varyings);
+ std::vector<Varying> varyVector;
+ varyVector.reserve(size);
+ for (int i = 0; i < size; i++) {
+ jobject varying = env->GetObjectArrayElement(varyings, i);
+ auto name = (jstring)env->GetObjectField(varying, gVaryingInfo.name);
+ auto varyName = ScopedUtfChars(env, name);
+ Varying temp{Varying::Type(env->GetIntField(varying, gVaryingInfo.type)),
+ SkString(varyName.c_str())};
+ varyVector.push_back(std::move(temp));
+ }
+
+ return varyVector;
+}
+
+static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+ jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+ SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()));
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+ jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader,
+ jlong colorSpace) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpecResult = SkMeshSpecification::Make(
+ attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace));
+
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
+ jint vertexStride, jobjectArray varyingArray, jstring vertexShader,
+ jstring fragmentShader, jlong colorSpace, jint alphaType) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpecResult = SkMeshSpecification::Make(
+ attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()), GraphicsJNI::getNativeColorSpace(colorSpace),
+ SkAlphaType(alphaType));
+
+ if (meshSpecResult.specification.get() == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshSpecResult.specification.release());
+}
+
+static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
+ SkSafeUnref(meshSpec);
+}
+
+static jlong getMeshSpecificationFinalizer() {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
+}
+
+static const JNINativeMethod gMeshSpecificationMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getMeshSpecificationFinalizer},
+ {"nativeMake",
+ "([Landroid/graphics/MeshSpecification$Attribute;I[Landroid/graphics/"
+ "MeshSpecification$Varying;"
+ "Ljava/lang/String;Ljava/lang/String;)J",
+ (void*)Make},
+ {"nativeMakeWithCS",
+ "([Landroid/graphics/MeshSpecification$Attribute;I"
+ "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;J)J",
+ (void*)MakeWithCS},
+ {"nativeMakeWithAlpha",
+ "([Landroid/graphics/MeshSpecification$Attribute;I"
+ "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;JI)J",
+ (void*)MakeWithAlpha}};
+
+int register_android_graphics_MeshSpecification(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/MeshSpecification",
+ gMeshSpecificationMethods, NELEM(gMeshSpecificationMethods));
+
+ gAttributeInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Attribute");
+ gAttributeInfo.type = env->GetFieldID(gAttributeInfo.clazz, "mType", "I");
+ gAttributeInfo.offset = env->GetFieldID(gAttributeInfo.clazz, "mOffset", "I");
+ gAttributeInfo.name = env->GetFieldID(gAttributeInfo.clazz, "mName", "Ljava/lang/String;");
+
+ gVaryingInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Varying");
+ gVaryingInfo.type = env->GetFieldID(gVaryingInfo.clazz, "mType", "I");
+ gVaryingInfo.name = env->GetFieldID(gVaryingInfo.clazz, "mName", "Ljava/lang/String;");
+ return 0;
+}
+
+} // namespace android
diff --git a/libs/hwui/jni/Movie.cpp b/libs/hwui/jni/Movie.cpp
index bb8c99a73edf..7dfd8740b2d7 100644
--- a/libs/hwui/jni/Movie.cpp
+++ b/libs/hwui/jni/Movie.cpp
@@ -3,8 +3,8 @@
#include "GraphicsJNI.h"
#include <nativehelper/ScopedLocalRef.h>
#include "Movie.h"
+#include "SkRefCnt.h"
#include "SkStream.h"
-#include "SkUtils.h"
#include "Utils.h"
#include <androidfw/Asset.h>
diff --git a/libs/hwui/jni/Movie.h b/libs/hwui/jni/Movie.h
index 736890d5215e..02113dd58ec8 100644
--- a/libs/hwui/jni/Movie.h
+++ b/libs/hwui/jni/Movie.h
@@ -13,6 +13,7 @@
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkRefCnt.h"
+#include "SkTypes.h"
class SkStreamRewindable;
diff --git a/libs/hwui/jni/MovieImpl.cpp b/libs/hwui/jni/MovieImpl.cpp
index ae9e04e617b0..abb75fa99c94 100644
--- a/libs/hwui/jni/MovieImpl.cpp
+++ b/libs/hwui/jni/MovieImpl.cpp
@@ -5,11 +5,12 @@
* found in the LICENSE file.
*/
#include "Movie.h"
-#include "SkCanvas.h"
-#include "SkPaint.h"
+#include "SkBitmap.h"
+#include "SkStream.h"
+#include "SkTypes.h"
// We should never see this in normal operation since our time values are
-// 0-based. So we use it as a sentinal.
+// 0-based. So we use it as a sentinel.
#define UNINITIALIZED_MSEC ((SkMSec)-1)
Movie::Movie()
@@ -81,8 +82,6 @@ const SkBitmap& Movie::bitmap()
////////////////////////////////////////////////////////////////////
-#include "SkStream.h"
-
Movie* Movie::DecodeMemory(const void* data, size_t length) {
SkMemoryStream stream(data, length, false);
return Movie::DecodeStream(&stream);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index 08fc80fbdafd..d50a8a22b5cb 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -24,8 +24,10 @@
#include <hwui/Paint.h>
#include <utils/Log.h>
+#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkLatticeIter.h"
+#include "SkRect.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
#include "NinePatchPeeker.h"
diff --git a/libs/hwui/jni/NinePatchPeeker.cpp b/libs/hwui/jni/NinePatchPeeker.cpp
index 9171fc687276..d85ede5dc6d2 100644
--- a/libs/hwui/jni/NinePatchPeeker.cpp
+++ b/libs/hwui/jni/NinePatchPeeker.cpp
@@ -16,7 +16,7 @@
#include "NinePatchPeeker.h"
-#include <SkBitmap.h>
+#include <SkScalar.h>
#include <cutils/compiler.h>
using namespace android;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 293e28022e47..13357fa25e8c 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -26,12 +26,14 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include "SkColorFilter.h"
+#include "SkColorSpace.h"
#include "SkFont.h"
#include "SkFontMetrics.h"
#include "SkFontTypes.h"
#include "SkMaskFilter.h"
#include "SkPath.h"
#include "SkPathEffect.h"
+#include "SkPathUtils.h"
#include "SkShader.h"
#include "SkBlendMode.h"
#include "unicode/uloc.h"
@@ -495,17 +497,32 @@ namespace PaintGlue {
return true;
}
- static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
- jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
+ static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
+ const jchar buf[], jint start, jint count, jint bufSize,
+ jboolean isRtl, jint offset, jfloatArray advances,
+ jint advancesIndex) {
+ if (advances) {
+ size_t advancesLength = env->GetArrayLength(advances);
+ if ((size_t)(count + advancesIndex) > advancesLength) {
+ doThrowAIOOBE(env);
+ return 0;
+ }
+ }
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
- if (offset == start + count) {
+ if (offset == start + count && advances == nullptr) {
return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
bufSize, nullptr);
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
advancesArray.get());
- return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
+
+ float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
+ if (advances) {
+ minikin::distributeAdvances(advancesArray.get(), buf, start, count);
+ env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
+ }
+ return result;
}
static jfloat getRunAdvance___CIIIIZI_F(JNIEnv *env, jclass, jlong paintHandle, jcharArray text,
@@ -513,9 +530,23 @@ namespace PaintGlue {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
- jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart,
- start - contextStart, end - start, contextEnd - contextStart, isRtl,
- offset - contextStart);
+ jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
+ start - contextStart, end - start, contextEnd - contextStart,
+ isRtl, offset - contextStart, nullptr, 0);
+ return result;
+ }
+
+ static jfloat getRunCharacterAdvance___CIIIIZI_FI_F(JNIEnv* env, jclass, jlong paintHandle,
+ jcharArray text, jint start, jint end,
+ jint contextStart, jint contextEnd,
+ jboolean isRtl, jint offset,
+ jfloatArray advances, jint advancesIndex) {
+ const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ const Typeface* typeface = paint->getAndroidTypeface();
+ ScopedCharArrayRO textArray(env, text);
+ jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
+ start - contextStart, end - start, contextEnd - contextStart,
+ isRtl, offset - contextStart, advances, advancesIndex);
return result;
}
@@ -778,7 +809,7 @@ namespace PaintGlue {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
SkPath* src = reinterpret_cast<SkPath*>(srcHandle);
SkPath* dst = reinterpret_cast<SkPath*>(dstHandle);
- return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
+ return skpathutils::FillPathWithPaint(*src, *obj, dst) ? JNI_TRUE : JNI_FALSE;
}
static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
@@ -1032,113 +1063,112 @@ namespace PaintGlue {
}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
- {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer},
- {"nInit","()J", (void*) PaintGlue::init},
- {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
- {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC},
- {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
- {"nGetTextAdvances","(J[CIIIII[FI)F",
- (void*) PaintGlue::getTextAdvances___CIIIII_FI},
- {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F",
- (void*) PaintGlue::getTextAdvances__StringIIIII_FI},
-
- {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C},
- {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I",
- (void*) PaintGlue::getTextRunCursor__String},
- {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
- {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
- {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V",
- (void*) PaintGlue::getStringBounds },
- {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
- (void*) PaintGlue::getCharArrayBounds },
- {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
- {"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 ----------------------
-
- {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales},
- {"nSetFontFeatureSettings","(JLjava/lang/String;)V",
- (void*) PaintGlue::setFontFeatureSettings},
- {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
- (void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
- (void*)PaintGlue::getFontMetricsInt},
-
- // --------------- @CriticalNative ------------------
-
- {"nReset","(J)V", (void*) PaintGlue::reset},
- {"nSet","(JJ)V", (void*) PaintGlue::assign},
- {"nGetFlags","(J)I", (void*) PaintGlue::getFlags},
- {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags},
- {"nGetHinting","(J)I", (void*) PaintGlue::getHinting},
- {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting},
- {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias},
- {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText},
- {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText},
- {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText},
- {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText},
- {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText},
- {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap},
- {"nSetDither","(JZ)V", (void*) PaintGlue::setDither},
- {"nGetStyle","(J)I", (void*) PaintGlue::getStyle},
- {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle},
- {"nSetColor","(JI)V", (void*) PaintGlue::setColor},
- {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong},
- {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
- {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth},
- {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth},
- {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter},
- {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter},
- {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap},
- {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap},
- {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
- {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
- {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
- {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
- {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
- {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
- {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter},
- {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface},
- {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign},
- {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign},
- {"nSetTextLocalesByMinikinLocaleListId","(JI)V",
- (void*) PaintGlue::setTextLocalesByMinikinLocaleListId},
- {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight},
- {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize},
- {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize},
- {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX},
- {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX},
- {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX},
- {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX},
- {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing},
- {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing},
- {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing},
- {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing},
- {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit},
- {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit},
- {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit},
- {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit},
- {"nAscent","(J)F", (void*) PaintGlue::ascent},
- {"nDescent","(J)F", (void*) PaintGlue::descent},
- {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition},
- {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness},
- {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition},
- {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness},
- {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer},
- {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
- {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
+ {"nGetNativeFinalizer", "()J", (void*)PaintGlue::getNativeFinalizer},
+ {"nInit", "()J", (void*)PaintGlue::init},
+ {"nInitWithPaint", "(J)J", (void*)PaintGlue::initWithPaint},
+ {"nBreakText", "(J[CIIFI[F)I", (void*)PaintGlue::breakTextC},
+ {"nBreakText", "(JLjava/lang/String;ZFI[F)I", (void*)PaintGlue::breakTextS},
+ {"nGetTextAdvances", "(J[CIIIII[FI)F", (void*)PaintGlue::getTextAdvances___CIIIII_FI},
+ {"nGetTextAdvances", "(JLjava/lang/String;IIIII[FI)F",
+ (void*)PaintGlue::getTextAdvances__StringIIIII_FI},
+
+ {"nGetTextRunCursor", "(J[CIIIII)I", (void*)PaintGlue::getTextRunCursor___C},
+ {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I",
+ (void*)PaintGlue::getTextRunCursor__String},
+ {"nGetTextPath", "(JI[CIIFFJ)V", (void*)PaintGlue::getTextPath___C},
+ {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*)PaintGlue::getTextPath__String},
+ {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V",
+ (void*)PaintGlue::getStringBounds},
+ {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
+ (void*)PaintGlue::getCharArrayBounds},
+ {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
+ {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
+ {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+ (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_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 ----------------------
+
+ {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
+ {"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
+ (void*)PaintGlue::setFontFeatureSettings},
+ {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+ (void*)PaintGlue::getFontMetrics},
+ {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+ (void*)PaintGlue::getFontMetricsInt},
+
+ // --------------- @CriticalNative ------------------
+
+ {"nReset", "(J)V", (void*)PaintGlue::reset},
+ {"nSet", "(JJ)V", (void*)PaintGlue::assign},
+ {"nGetFlags", "(J)I", (void*)PaintGlue::getFlags},
+ {"nSetFlags", "(JI)V", (void*)PaintGlue::setFlags},
+ {"nGetHinting", "(J)I", (void*)PaintGlue::getHinting},
+ {"nSetHinting", "(JI)V", (void*)PaintGlue::setHinting},
+ {"nSetAntiAlias", "(JZ)V", (void*)PaintGlue::setAntiAlias},
+ {"nSetSubpixelText", "(JZ)V", (void*)PaintGlue::setSubpixelText},
+ {"nSetLinearText", "(JZ)V", (void*)PaintGlue::setLinearText},
+ {"nSetUnderlineText", "(JZ)V", (void*)PaintGlue::setUnderlineText},
+ {"nSetStrikeThruText", "(JZ)V", (void*)PaintGlue::setStrikeThruText},
+ {"nSetFakeBoldText", "(JZ)V", (void*)PaintGlue::setFakeBoldText},
+ {"nSetFilterBitmap", "(JZ)V", (void*)PaintGlue::setFilterBitmap},
+ {"nSetDither", "(JZ)V", (void*)PaintGlue::setDither},
+ {"nGetStyle", "(J)I", (void*)PaintGlue::getStyle},
+ {"nSetStyle", "(JI)V", (void*)PaintGlue::setStyle},
+ {"nSetColor", "(JI)V", (void*)PaintGlue::setColor},
+ {"nSetColor", "(JJJ)V", (void*)PaintGlue::setColorLong},
+ {"nSetAlpha", "(JI)V", (void*)PaintGlue::setAlpha},
+ {"nGetStrokeWidth", "(J)F", (void*)PaintGlue::getStrokeWidth},
+ {"nSetStrokeWidth", "(JF)V", (void*)PaintGlue::setStrokeWidth},
+ {"nGetStrokeMiter", "(J)F", (void*)PaintGlue::getStrokeMiter},
+ {"nSetStrokeMiter", "(JF)V", (void*)PaintGlue::setStrokeMiter},
+ {"nGetStrokeCap", "(J)I", (void*)PaintGlue::getStrokeCap},
+ {"nSetStrokeCap", "(JI)V", (void*)PaintGlue::setStrokeCap},
+ {"nGetStrokeJoin", "(J)I", (void*)PaintGlue::getStrokeJoin},
+ {"nSetStrokeJoin", "(JI)V", (void*)PaintGlue::setStrokeJoin},
+ {"nGetFillPath", "(JJJ)Z", (void*)PaintGlue::getFillPath},
+ {"nSetShader", "(JJ)J", (void*)PaintGlue::setShader},
+ {"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter},
+ {"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode},
+ {"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect},
+ {"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter},
+ {"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface},
+ {"nGetTextAlign", "(J)I", (void*)PaintGlue::getTextAlign},
+ {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
+ {"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
+ (void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
+ {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
+ {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+ {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
+ {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
+ {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
+ {"nSetTextScaleX", "(JF)V", (void*)PaintGlue::setTextScaleX},
+ {"nGetTextSkewX", "(J)F", (void*)PaintGlue::getTextSkewX},
+ {"nSetTextSkewX", "(JF)V", (void*)PaintGlue::setTextSkewX},
+ {"nGetLetterSpacing", "(J)F", (void*)PaintGlue::getLetterSpacing},
+ {"nSetLetterSpacing", "(JF)V", (void*)PaintGlue::setLetterSpacing},
+ {"nGetWordSpacing", "(J)F", (void*)PaintGlue::getWordSpacing},
+ {"nSetWordSpacing", "(JF)V", (void*)PaintGlue::setWordSpacing},
+ {"nGetStartHyphenEdit", "(J)I", (void*)PaintGlue::getStartHyphenEdit},
+ {"nGetEndHyphenEdit", "(J)I", (void*)PaintGlue::getEndHyphenEdit},
+ {"nSetStartHyphenEdit", "(JI)V", (void*)PaintGlue::setStartHyphenEdit},
+ {"nSetEndHyphenEdit", "(JI)V", (void*)PaintGlue::setEndHyphenEdit},
+ {"nAscent", "(J)F", (void*)PaintGlue::ascent},
+ {"nDescent", "(J)F", (void*)PaintGlue::descent},
+ {"nGetUnderlinePosition", "(J)F", (void*)PaintGlue::getUnderlinePosition},
+ {"nGetUnderlineThickness", "(J)F", (void*)PaintGlue::getUnderlineThickness},
+ {"nGetStrikeThruPosition", "(J)F", (void*)PaintGlue::getStrikeThruPosition},
+ {"nGetStrikeThruThickness", "(J)F", (void*)PaintGlue::getStrikeThruThickness},
+ {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer},
+ {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
+ {"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/Path.cpp b/libs/hwui/jni/Path.cpp
index d67bcf221681..a5e04763d885 100644
--- a/libs/hwui/jni/Path.cpp
+++ b/libs/hwui/jni/Path.cpp
@@ -102,6 +102,18 @@ public:
obj->rQuadTo(dx1, dy1, dx2, dy2);
}
+ static void conicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2,
+ jfloat y2, jfloat weight) {
+ SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+ obj->conicTo(x1, y1, x2, y2, weight);
+ }
+
+ static void rConicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1,
+ jfloat dx2, jfloat dy2, jfloat weight) {
+ SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+ obj->rConicTo(dx1, dy1, dx2, dy2, weight);
+ }
+
static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1,
jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -170,11 +182,7 @@ public:
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
SkPathDirection dir = static_cast<SkPathDirection>(dirHandle);
AutoJavaFloatArray afa(env, array, 8);
-#ifdef SK_SCALAR_IS_FLOAT
const float* src = afa.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
obj->addRoundRect(rect, src, dir);
}
@@ -209,6 +217,14 @@ public:
obj->setLastPt(dx, dy);
}
+ static jboolean interpolate(JNIEnv* env, jclass clazz, jlong startHandle, jlong endHandle,
+ jfloat t, jlong interpolatedHandle) {
+ SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+ SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+ SkPath* interpolatedPath = reinterpret_cast<SkPath*>(interpolatedHandle);
+ return startPath->interpolate(*endPath, t, interpolatedPath);
+ }
+
static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle,
jlong dstHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -473,6 +489,16 @@ public:
// ---------------- @CriticalNative -------------------------
+ static jint getGenerationID(CRITICAL_JNI_PARAMS_COMMA jlong pathHandle) {
+ return (reinterpret_cast<SkPath*>(pathHandle)->getGenerationID());
+ }
+
+ static jboolean isInterpolatable(CRITICAL_JNI_PARAMS_COMMA jlong startHandle, jlong endHandle) {
+ SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+ SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+ return startPath->isInterpolatable(*endPath);
+ }
+
static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
obj->reset();
@@ -506,48 +532,53 @@ public:
};
static const JNINativeMethod methods[] = {
- {"nInit","()J", (void*) SkPathGlue::init},
- {"nInit","(J)J", (void*) SkPathGlue::init_Path},
- {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer},
- {"nSet","(JJ)V", (void*) SkPathGlue::set},
- {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds},
- {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve},
- {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF},
- {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo},
- {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF},
- {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo},
- {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF},
- {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo},
- {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF},
- {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo},
- {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo},
- {"nClose","(J)V", (void*) SkPathGlue::close},
- {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect},
- {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval},
- {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle},
- {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc},
- {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY},
- {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8},
- {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF},
- {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path},
- {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix},
- {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF},
- {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint},
- {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath},
- {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix},
- {"nOp","(JJIJ)Z", (void*) SkPathGlue::op},
- {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate},
-
- // ------- @FastNative below here ----------------------
- {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect},
-
- // ------- @CriticalNative below here ------------------
- {"nReset","(J)V", (void*) SkPathGlue::reset},
- {"nRewind","(J)V", (void*) SkPathGlue::rewind},
- {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty},
- {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex},
- {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType},
- {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType},
+ {"nInit", "()J", (void*)SkPathGlue::init},
+ {"nInit", "(J)J", (void*)SkPathGlue::init_Path},
+ {"nGetFinalizer", "()J", (void*)SkPathGlue::getFinalizer},
+ {"nSet", "(JJ)V", (void*)SkPathGlue::set},
+ {"nComputeBounds", "(JLandroid/graphics/RectF;)V", (void*)SkPathGlue::computeBounds},
+ {"nIncReserve", "(JI)V", (void*)SkPathGlue::incReserve},
+ {"nMoveTo", "(JFF)V", (void*)SkPathGlue::moveTo__FF},
+ {"nRMoveTo", "(JFF)V", (void*)SkPathGlue::rMoveTo},
+ {"nLineTo", "(JFF)V", (void*)SkPathGlue::lineTo__FF},
+ {"nRLineTo", "(JFF)V", (void*)SkPathGlue::rLineTo},
+ {"nQuadTo", "(JFFFF)V", (void*)SkPathGlue::quadTo__FFFF},
+ {"nRQuadTo", "(JFFFF)V", (void*)SkPathGlue::rQuadTo},
+ {"nConicTo", "(JFFFFF)V", (void*)SkPathGlue::conicTo},
+ {"nRConicTo", "(JFFFFF)V", (void*)SkPathGlue::rConicTo},
+ {"nCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::cubicTo__FFFFFF},
+ {"nRCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::rCubicTo},
+ {"nArcTo", "(JFFFFFFZ)V", (void*)SkPathGlue::arcTo},
+ {"nClose", "(J)V", (void*)SkPathGlue::close},
+ {"nAddRect", "(JFFFFI)V", (void*)SkPathGlue::addRect},
+ {"nAddOval", "(JFFFFI)V", (void*)SkPathGlue::addOval},
+ {"nAddCircle", "(JFFFI)V", (void*)SkPathGlue::addCircle},
+ {"nAddArc", "(JFFFFFF)V", (void*)SkPathGlue::addArc},
+ {"nAddRoundRect", "(JFFFFFFI)V", (void*)SkPathGlue::addRoundRectXY},
+ {"nAddRoundRect", "(JFFFF[FI)V", (void*)SkPathGlue::addRoundRect8},
+ {"nAddPath", "(JJFF)V", (void*)SkPathGlue::addPath__PathFF},
+ {"nAddPath", "(JJ)V", (void*)SkPathGlue::addPath__Path},
+ {"nAddPath", "(JJJ)V", (void*)SkPathGlue::addPath__PathMatrix},
+ {"nInterpolate", "(JJFJ)Z", (void*)SkPathGlue::interpolate},
+ {"nOffset", "(JFF)V", (void*)SkPathGlue::offset__FF},
+ {"nSetLastPoint", "(JFF)V", (void*)SkPathGlue::setLastPoint},
+ {"nTransform", "(JJJ)V", (void*)SkPathGlue::transform__MatrixPath},
+ {"nTransform", "(JJ)V", (void*)SkPathGlue::transform__Matrix},
+ {"nOp", "(JJIJ)Z", (void*)SkPathGlue::op},
+ {"nApproximate", "(JF)[F", (void*)SkPathGlue::approximate},
+
+ // ------- @FastNative below here ----------------------
+ {"nIsRect", "(JLandroid/graphics/RectF;)Z", (void*)SkPathGlue::isRect},
+
+ // ------- @CriticalNative below here ------------------
+ {"nGetGenerationID", "(J)I", (void*)SkPathGlue::getGenerationID},
+ {"nIsInterpolatable", "(JJ)Z", (void*)SkPathGlue::isInterpolatable},
+ {"nReset", "(J)V", (void*)SkPathGlue::reset},
+ {"nRewind", "(J)V", (void*)SkPathGlue::rewind},
+ {"nIsEmpty", "(J)Z", (void*)SkPathGlue::isEmpty},
+ {"nIsConvex", "(J)Z", (void*)SkPathGlue::isConvex},
+ {"nGetFillType", "(J)I", (void*)SkPathGlue::getFillType},
+ {"nSetFillType", "(JI)V", (void*)SkPathGlue::setFillType},
};
int register_android_graphics_Path(JNIEnv* env) {
diff --git a/libs/hwui/jni/PathEffect.cpp b/libs/hwui/jni/PathEffect.cpp
index f99bef7b7d58..3dbe1a67f52e 100644
--- a/libs/hwui/jni/PathEffect.cpp
+++ b/libs/hwui/jni/PathEffect.cpp
@@ -35,11 +35,7 @@ public:
jfloatArray intervalArray, jfloat phase) {
AutoJavaFloatArray autoInterval(env, intervalArray);
int count = autoInterval.length() & ~1; // even number
-#ifdef SK_SCALAR_IS_FLOAT
- SkScalar* intervals = autoInterval.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
+ SkScalar* intervals = autoInterval.ptr();
SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release();
return reinterpret_cast<jlong>(effect);
}
diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp
new file mode 100644
index 000000000000..3884342d8d37
--- /dev/null
+++ b/libs/hwui/jni/PathIterator.cpp
@@ -0,0 +1,81 @@
+/* libs/android_runtime/android/graphics/PathMeasure.cpp
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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 <log/log.h>
+
+#include "GraphicsJNI.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+
+namespace android {
+
+class SkPathIteratorGlue {
+public:
+ static void finalizer(SkPath::RawIter* obj) { delete obj; }
+
+ static jlong getFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer));
+ }
+
+ static jlong create(JNIEnv* env, jobject clazz, jlong pathHandle) {
+ const SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ return reinterpret_cast<jlong>(new SkPath::RawIter(*path));
+ }
+
+ // ---------------- @CriticalNative -------------------------
+
+ static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) {
+ SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+ return iterator->peek();
+ }
+
+ static jint next(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle, jlong pointsArray) {
+ static_assert(SkPath::kMove_Verb == 0, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kLine_Verb == 1, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kQuad_Verb == 2, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kConic_Verb == 3, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kCubic_Verb == 4, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kClose_Verb == 5, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kDone_Verb == 6, "SkPath::Verb unexpected index");
+
+ SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+ float* points = reinterpret_cast<float*>(pointsArray);
+ SkPath::Verb verb =
+ static_cast<SkPath::Verb>(iterator->next(reinterpret_cast<SkPoint*>(points)));
+ if (verb == SkPath::kConic_Verb) {
+ float weight = iterator->conicWeight();
+ points[6] = weight;
+ }
+ return static_cast<int>(verb);
+ }
+};
+
+static const JNINativeMethod methods[] = {
+ {"nCreate", "(J)J", (void*)SkPathIteratorGlue::create},
+ {"nGetFinalizer", "()J", (void*)SkPathIteratorGlue::getFinalizer},
+
+ // ------- @CriticalNative below here ------------------
+
+ {"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek},
+ {"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next},
+};
+
+int register_android_graphics_PathIterator(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/PathIterator", methods, NELEM(methods));
+}
+
+} // namespace android
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index 213f35a81b88..f3db1705e694 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -15,6 +15,7 @@
*/
#include "Bitmap.h"
#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
#include "SkImageFilter.h"
#include "SkImageFilters.h"
#include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/ScopedParcel.cpp b/libs/hwui/jni/ScopedParcel.cpp
new file mode 100644
index 000000000000..b0f5423813b7
--- /dev/null
+++ b/libs/hwui/jni/ScopedParcel.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ScopedParcel.h"
+
+#ifdef __ANDROID__ // Layoutlib does not support parcel
+
+using namespace android;
+
+int32_t ScopedParcel::readInt32() {
+ int32_t temp = 0;
+ // TODO: This behavior-matches what android::Parcel does
+ // but this should probably be better
+ if (AParcel_readInt32(mParcel, &temp) != STATUS_OK) {
+ temp = 0;
+ }
+ return temp;
+}
+
+uint32_t ScopedParcel::readUint32() {
+ uint32_t temp = 0;
+ // TODO: This behavior-matches what android::Parcel does
+ // but this should probably be better
+ if (AParcel_readUint32(mParcel, &temp) != STATUS_OK) {
+ temp = 0;
+ }
+ return temp;
+}
+
+float ScopedParcel::readFloat() {
+ float temp = 0.;
+ if (AParcel_readFloat(mParcel, &temp) != STATUS_OK) {
+ temp = 0.;
+ }
+ return temp;
+}
+
+std::optional<sk_sp<SkData>> ScopedParcel::readData() {
+ struct Data {
+ void* ptr = nullptr;
+ size_t size = 0;
+ } data;
+ auto error = AParcel_readByteArray(
+ mParcel, &data, [](void* arrayData, int32_t length, int8_t** outBuffer) -> bool {
+ Data* data = reinterpret_cast<Data*>(arrayData);
+ if (length > 0) {
+ data->ptr = sk_malloc_canfail(length);
+ if (!data->ptr) {
+ return false;
+ }
+ *outBuffer = reinterpret_cast<int8_t*>(data->ptr);
+ data->size = length;
+ }
+ return true;
+ });
+ if (error != STATUS_OK || data.size <= 0) {
+ sk_free(data.ptr);
+ return std::nullopt;
+ } else {
+ return SkData::MakeFromMalloc(data.ptr, data.size);
+ }
+}
+
+void ScopedParcel::writeData(const std::optional<sk_sp<SkData>>& optData) {
+ if (optData) {
+ const auto& data = *optData;
+ AParcel_writeByteArray(mParcel, reinterpret_cast<const int8_t*>(data->data()),
+ data->size());
+ } else {
+ AParcel_writeByteArray(mParcel, nullptr, -1);
+ }
+}
+#endif // __ANDROID__ // Layoutlib does not support parcel
diff --git a/libs/hwui/jni/ScopedParcel.h b/libs/hwui/jni/ScopedParcel.h
new file mode 100644
index 000000000000..fd8d6a210f0f
--- /dev/null
+++ b/libs/hwui/jni/ScopedParcel.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "SkData.h"
+
+#ifdef __ANDROID__ // Layoutlib does not support parcel
+#include <android-base/unique_fd.h>
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
+#include <android/binder_parcel_platform.h>
+#include <cutils/ashmem.h>
+#include <renderthread/RenderProxy.h>
+
+class ScopedParcel {
+public:
+ explicit ScopedParcel(JNIEnv* env, jobject parcel) {
+ mParcel = AParcel_fromJavaParcel(env, parcel);
+ }
+
+ ~ScopedParcel() { AParcel_delete(mParcel); }
+
+ int32_t readInt32();
+
+ uint32_t readUint32();
+
+ float readFloat();
+
+ void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
+
+ void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
+
+ void writeFloat(float value) { AParcel_writeFloat(mParcel, value); }
+
+ bool allowFds() const { return AParcel_getAllowFds(mParcel); }
+
+ std::optional<sk_sp<SkData>> readData();
+
+ void writeData(const std::optional<sk_sp<SkData>>& optData);
+
+ AParcel* get() { return mParcel; }
+
+private:
+ AParcel* mParcel;
+};
+
+enum class BlobType : int32_t {
+ IN_PLACE,
+ ASHMEM,
+};
+
+#endif // __ANDROID__ // Layoutlib does not support parcel \ No newline at end of file
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 0bbd8a8cf97c..7eb79be6f55b 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,22 +1,34 @@
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"
+#include <vector>
+
+#include "Gainmap.h"
#include "GraphicsJNI.h"
+#include "SkBitmap.h"
+#include "SkBlendMode.h"
+#include "SkColor.h"
#include "SkColorFilter.h"
#include "SkGradientShader.h"
+#include "SkImage.h"
#include "SkImagePriv.h"
+#include "SkMatrix.h"
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+#include "SkSamplingOptions.h"
+#include "SkScalar.h"
#include "SkShader.h"
-#include "SkBlendMode.h"
+#include "SkString.h"
+#include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
#include "include/effects/SkRuntimeEffect.h"
-#include <vector>
-
using namespace android::uirenderer;
/**
* By default Skia gradients will interpolate their colors in unpremul space
* and then premultiply each of the results. We must set this flag to preserve
- * backwards compatiblity by premultiplying the colors of the gradient first,
+ * backwards compatibility by premultiplying the colors of the gradient first,
* and then interpolating between them.
*/
static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColorsInPremul_Flag;
@@ -42,12 +54,7 @@ static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue
static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
{
AutoJavaFloatArray autoHSV(env, hsvArray, 3);
-#ifdef SK_SCALAR_IS_FLOAT
- SkScalar* hsv = autoHSV.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
-
+ SkScalar* hsv = autoHSV.ptr();
return static_cast<jint>(SkHSVToColor(alpha, hsv));
}
@@ -61,25 +68,35 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
}
-///////////////////////////////////////////////////////////////////////////////////////////////
-
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool filter,
- bool isDirectSampled) {
+static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+ jint tileModeX, jint tileModeY, bool isDirectSampled,
+ const SkSamplingOptions& sampling) {
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
sk_sp<SkImage> image;
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
- image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+ auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+ image = bitmap.makeImage();
+
+ if (!isDirectSampled && bitmap.hasGainmap()) {
+ sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (gainmapShader) {
+ if (matrix) {
+ gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+ }
+ return reinterpret_cast<jlong>(gainmapShader.release());
+ }
+ }
}
if (!image.get()) {
SkBitmap bitmap;
image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
}
- SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
- SkMipmapMode::kNone);
+
sk_sp<SkShader> shader;
if (isDirectSampled) {
shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
@@ -97,6 +114,26 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j
///////////////////////////////////////////////////////////////////////////////////////////////
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+ jint tileModeX, jint tileModeY, bool filter,
+ bool isDirectSampled) {
+ SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
+ SkMipmapMode::kNone);
+ return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
+ isDirectSampled, sampling);
+}
+
+static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
+ jlong bitmapHandle, jint tileModeX,
+ jint tileModeY, jint maxAniso,
+ bool isDirectSampled) {
+ auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
+ return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
+ isDirectSampled, sampling);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
const size_t count = env->GetArrayLength(colorArray);
const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -122,11 +159,7 @@ static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr,
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
@@ -166,11 +199,7 @@ static jlong RadialGradient_create(JNIEnv* env,
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
auto skTileMode = static_cast<SkTileMode>(tileMode);
@@ -198,11 +227,7 @@ static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat
std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
AutoJavaFloatArray autoPos(env, jpositions, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
SkScalar* pos = autoPos.ptr();
-#else
- #error Need to convert float array to SkScalar array before calling the following function.
-#endif
sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
@@ -398,6 +423,8 @@ static const JNINativeMethod gShaderMethods[] = {
static const JNINativeMethod gBitmapShaderMethods[] = {
{"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
+ {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+
};
static const JNINativeMethod gLinearGradientMethods[] = {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index d86d9ee56f4c..209b35c5537c 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -20,18 +20,21 @@
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
#include <minikin/FontFileParser.h>
+#include <minikin/LocaleList.h>
+#include <minikin/MinikinFontFactory.h>
#include <minikin/SystemFonts.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
+
+#include <mutex>
+#include <unordered_map>
+
#include "FontUtils.h"
#include "GraphicsJNI.h"
#include "SkData.h"
#include "SkTypeface.h"
#include "fonts/Font.h"
-#include <mutex>
-#include <unordered_map>
-
#ifdef __ANDROID__
#include <sys/stat.h>
#endif
@@ -106,27 +109,14 @@ static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
jlong fallbackPtr, int weight, int italic) {
ScopedLongArrayRO families(env, familyArray);
- std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
- if (typeface != nullptr) {
- const std::vector<std::shared_ptr<minikin::FontFamily>>& fallbackFamilies =
- toTypeface(fallbackPtr)->fFontCollection->getFamilies();
- familyVec.reserve(families.size() + fallbackFamilies.size());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
- for (size_t i = 0; i < fallbackFamilies.size(); i++) {
- familyVec.emplace_back(fallbackFamilies[i]);
- }
- } else {
- familyVec.reserve(families.size());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
+ std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
+ familyVec.reserve(families.size());
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+ familyVec.emplace_back(family->family);
}
- return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
+ return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface));
}
// CriticalNative
@@ -137,15 +127,13 @@ static void Typeface_setDefault(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
Typeface* face = toTypeface(faceHandle);
- const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags();
- const size_t length = tagSet.size();
+ const size_t length = face->fFontCollection->getSupportedAxesCount();
if (length == 0) {
return nullptr;
}
std::vector<jint> tagVec(length);
- int index = 0;
- for (const auto& tag : tagSet) {
- tagVec[index++] = tag;
+ for (size_t i = 0; i < length; i++) {
+ tagVec[i] = face->fFontCollection->getSupportedAxisAt(i);
}
std::sort(tagVec.begin(), tagVec.end());
const jintArray result = env->NewIntArray(length);
@@ -204,9 +192,18 @@ static sk_sp<SkData> makeSkDataCached(const std::string& path, bool hasVerity) {
return entry;
}
-static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader);
+class MinikinFontSkiaFactory : minikin::MinikinFontFactory {
+private:
+ MinikinFontSkiaFactory() : MinikinFontFactory() { MinikinFontFactory::setInstance(this); }
+
+public:
+ static void init() { static MinikinFontSkiaFactory factory; }
+ void skip(minikin::BufferReader* reader) const override;
+ std::shared_ptr<minikin::MinikinFont> create(minikin::BufferReader reader) const override;
+ void write(minikin::BufferWriter* writer, const minikin::MinikinFont* typeface) const override;
+};
-static minikin::Font::TypefaceLoader* readMinikinFontSkia(minikin::BufferReader* reader) {
+void MinikinFontSkiaFactory::skip(minikin::BufferReader* reader) const {
// Advance reader's position.
reader->skipString(); // fontPath
reader->skip<int>(); // fontIndex
@@ -216,10 +213,10 @@ static minikin::Font::TypefaceLoader* readMinikinFontSkia(minikin::BufferReader*
reader->skip<uint32_t>(); // expectedFontRevision
reader->skipString(); // expectedPostScriptName
}
- return &loadMinikinFontSkia;
}
-static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader reader) {
+std::shared_ptr<minikin::MinikinFont> MinikinFontSkiaFactory::create(
+ minikin::BufferReader reader) const {
std::string_view fontPath = reader.readString();
std::string path(fontPath.data(), fontPath.size());
ATRACE_FORMAT("Loading font %s", path.c_str());
@@ -268,8 +265,8 @@ static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::Buffer
return minikinFont;
}
-static void writeMinikinFontSkia(minikin::BufferWriter* writer,
- const minikin::MinikinFont* typeface) {
+void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer,
+ const minikin::MinikinFont* typeface) const {
// When you change the format of font metadata, please update code to parse
// typefaceMetadataReader() in
// frameworks/base/libs/hwui/jni/fonts/Font.cpp too.
@@ -293,7 +290,9 @@ static void writeMinikinFontSkia(minikin::BufferWriter* writer,
}
}
-static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {
+static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint position,
+ jlongArray faceHandles) {
+ MinikinFontSkiaFactory::init();
ScopedLongArrayRO faces(env, faceHandles);
std::vector<Typeface*> typefaces;
typefaces.reserve(faces.size());
@@ -301,7 +300,12 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA
typefaces.push_back(toTypeface(faces[i]));
}
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- minikin::BufferWriter writer(addr);
+ if (addr != nullptr &&
+ reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return 0;
+ }
+ minikin::BufferWriter writer(addr, position);
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections;
std::unordered_map<std::shared_ptr<minikin::FontCollection>, size_t> fcToIndex;
for (Typeface* typeface : typefaces) {
@@ -310,7 +314,7 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA
fontCollections.push_back(typeface->fFontCollection);
}
}
- minikin::FontCollection::writeVector<writeMinikinFontSkia>(&writer, fontCollections);
+ minikin::FontCollection::writeVector(&writer, fontCollections);
writer.write<uint32_t>(typefaces.size());
for (Typeface* typeface : typefaces) {
writer.write<uint32_t>(fcToIndex.find(typeface->fFontCollection)->second);
@@ -321,12 +325,20 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA
return static_cast<jint>(writer.size());
}
-static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
+static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, jint position) {
+ MinikinFontSkiaFactory::init();
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- if (addr == nullptr) return nullptr;
- minikin::BufferReader reader(addr);
+ if (addr == nullptr) {
+ ALOGE("Passed a null buffer.");
+ return nullptr;
+ }
+ if (reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return nullptr;
+ }
+ minikin::BufferReader reader(addr, position);
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections =
- minikin::FontCollection::readVector<readMinikinFontSkia>(&reader);
+ minikin::FontCollection::readVector(&reader);
uint32_t typefaceCount = reader.read<uint32_t>();
std::vector<jlong> faceHandles;
faceHandles.reserve(typefaceCount);
@@ -343,7 +355,6 @@ static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
return result;
}
-
static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring fieldName,
jobject typeface) {
ScopedUtfChars fieldNameChars(env, fieldName);
@@ -356,18 +367,6 @@ static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring f
env->SetStaticObjectField(cls, fid, typeface);
}
-// Critical Native
-static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
-}
-
-// Critical Native
-static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
- std::shared_ptr<minikin::FontFamily> family =
- toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
- return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
-}
-
// Regular JNI
static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
ScopedUtfChars filePath(env, jFilePath);
@@ -380,6 +379,12 @@ static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandl
minikin::SystemFonts::addFontMap(std::move(collection));
}
+// Fast Native
+static void Typeface_registerLocaleList(JNIEnv* env, jobject, jstring jLocales) {
+ ScopedUtfChars locales(env, jLocales);
+ minikin::registerLocaleList(locales.c_str());
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
@@ -397,14 +402,13 @@ static const JNINativeMethod gTypefaceMethods[] = {
{"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
{"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
(void*)Typeface_registerGenericFamily},
- {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
- {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+ {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;I[J)I", (void*)Typeface_writeTypefaces},
+ {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces},
{"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
(void*)Typeface_forceSetStaticFinalField},
- {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
- {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
{"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
{"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection},
+ {"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList},
};
int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/hwui/jni/Utils.cpp b/libs/hwui/jni/Utils.cpp
index 106c6db57e18..9f5a2145792c 100644
--- a/libs/hwui/jni/Utils.cpp
+++ b/libs/hwui/jni/Utils.cpp
@@ -15,8 +15,9 @@
*/
#include "Utils.h"
-#include "SkUtils.h"
#include "SkData.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
#include <inttypes.h>
#include <log/log.h>
diff --git a/libs/hwui/jni/Utils.h b/libs/hwui/jni/Utils.h
index 6cdf44d85a5a..f6e3a0eeaa0e 100644
--- a/libs/hwui/jni/Utils.h
+++ b/libs/hwui/jni/Utils.h
@@ -17,8 +17,11 @@
#ifndef _ANDROID_GRAPHICS_UTILS_H_
#define _ANDROID_GRAPHICS_UTILS_H_
+#include "SkRefCnt.h"
#include "SkStream.h"
+class SkData;
+
#include <jni.h>
#include <androidfw/Asset.h>
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index d64d38a815f6..8874ef1d2fe0 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,5 +1,8 @@
+#undef LOG_TAG
+#define LOG_TAG "YuvToJpegEncoder"
+
#include "CreateJavaOutputStreamAdaptor.h"
-#include "SkJPEGWriteUtility.h"
+#include "SkStream.h"
#include "YuvToJpegEncoder.h"
#include <ui/PixelFormat.h>
#include <hardware/hardware.h>
@@ -8,6 +11,15 @@
#include <csetjmp>
+extern "C" {
+ // We need to include stdio.h before jpeg because jpeg does not include it, but uses FILE
+ // See https://github.com/libjpeg-turbo/libjpeg-turbo/issues/17
+ #include <stdio.h>
+ #include "jpeglib.h"
+ #include "jerror.h"
+ #include "jmorecfg.h"
+}
+
YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
// Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
// for now.
@@ -34,11 +46,64 @@ void error_exit(j_common_ptr cinfo) {
longjmp(err->jmp, 1);
}
+/*
+ * Destination struct for directing decompressed pixels to a SkStream.
+ */
+static constexpr size_t kMgrBufferSize = 1024;
+struct skstream_destination_mgr : jpeg_destination_mgr {
+ skstream_destination_mgr(SkWStream* stream);
+
+ SkWStream* const fStream;
+
+ uint8_t fBuffer[kMgrBufferSize];
+};
+
+static void sk_init_destination(j_compress_ptr cinfo) {
+ skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = kMgrBufferSize;
+}
+
+static boolean sk_empty_output_buffer(j_compress_ptr cinfo) {
+ skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
+
+ if (!dest->fStream->write(dest->fBuffer, kMgrBufferSize)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return FALSE;
+ }
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = kMgrBufferSize;
+ return TRUE;
+}
+
+static void sk_term_destination(j_compress_ptr cinfo) {
+ skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
+
+ size_t size = kMgrBufferSize - dest->free_in_buffer;
+ if (size > 0) {
+ if (!dest->fStream->write(dest->fBuffer, size)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return;
+ }
+ }
+
+ dest->fStream->flush();
+}
+
+skstream_destination_mgr::skstream_destination_mgr(SkWStream* stream)
+ : fStream(stream) {
+ this->init_destination = sk_init_destination;
+ this->empty_output_buffer = sk_empty_output_buffer;
+ this->term_destination = sk_term_destination;
+}
+
bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
int height, int* offsets, int jpegQuality) {
- jpeg_compress_struct cinfo;
- ErrorMgr err;
- skjpeg_destination_mgr sk_wstream(stream);
+ jpeg_compress_struct cinfo;
+ ErrorMgr err;
+ skstream_destination_mgr sk_wstream(stream);
cinfo.err = jpeg_std_error(&err.pub);
err.pub.error_exit = error_exit;
@@ -233,6 +298,99 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
}
///////////////////////////////////////////////////////////////////////////////
+using namespace android::jpegrecoverymap;
+
+jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
+ switch (aDataSpace & ADataSpace::STANDARD_MASK) {
+ case ADataSpace::STANDARD_BT709:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+ case ADataSpace::STANDARD_DCI_P3:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_P3;
+ case ADataSpace::STANDARD_BT2020:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+ default:
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested color gamut is not supported by JPEG/R.");
+ }
+
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED;
+}
+
+jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
+ int aDataSpace) {
+ switch (aDataSpace & ADataSpace::TRANSFER_MASK) {
+ case ADataSpace::TRANSFER_ST2084:
+ return jpegr_transfer_function::JPEGR_TF_PQ;
+ case ADataSpace::TRANSFER_HLG:
+ return jpegr_transfer_function::JPEGR_TF_HLG;
+ default:
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested HDR transfer function is not supported by JPEG/R.");
+ }
+
+ return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED;
+}
+
+bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
+ SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
+ int width, int height, int jpegQuality) {
+ // Check SDR color space. Now we only support SRGB transfer function
+ if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested SDR color space is not supported. Transfer function must be SRGB");
+ return false;
+ }
+
+ jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
+ jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
+ jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
+
+ if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
+ || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
+ || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) {
+ return false;
+ }
+
+ JpegR jpegREncoder;
+
+ jpegr_uncompressed_struct p010;
+ p010.data = hdr;
+ p010.width = width;
+ p010.height = height;
+ p010.colorGamut = hdrColorGamut;
+
+ jpegr_uncompressed_struct yuv420;
+ yuv420.data = sdr;
+ yuv420.width = width;
+ yuv420.height = height;
+ yuv420.colorGamut = sdrColorGamut;
+
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = width * height * sizeof(uint8_t);
+
+ std::unique_ptr<uint8_t[]> jpegr_data = std::make_unique<uint8_t[]>(jpegR.maxLength);
+ jpegR.data = jpegr_data.get();
+
+ if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
+ hdrTransferFunction,
+ &jpegR, jpegQuality, nullptr); success != android::OK) {
+ ALOGW("Encode JPEG/R failed, error code: %d.", success);
+ return false;
+ }
+
+ if (!stream->write(jpegR.data, jpegR.length)) {
+ ALOGW("Writing JPEG/R to stream failed.");
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
jint format, jint width, jint height, jintArray offsets,
jintArray strides, jint jpegQuality, jobject jstream,
@@ -256,11 +414,34 @@ static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
delete strm;
return result;
}
+
+static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
+ jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
+ jint width, jint height, jint quality, jobject jstream,
+ jbyteArray jstorage) {
+ jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
+ jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+ P010Yuv420ToJpegREncoder encoder;
+
+ jboolean result = JNI_FALSE;
+ if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
+ width, height, quality)) {
+ result = JNI_TRUE;
+ }
+
+ env->ReleaseByteArrayElements(inHdr, hdr, 0);
+ env->ReleaseByteArrayElements(inSdr, sdr, 0);
+ delete strm;
+ return result;
+}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gYuvImageMethods[] = {
{ "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
- (void*)YuvImage_compressToJpeg }
+ (void*)YuvImage_compressToJpeg },
+ { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z",
+ (void*)YuvImage_compressToJpegR }
};
int register_android_graphics_YuvImage(JNIEnv* env)
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 7e7b935df276..d22a26c83567 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -1,13 +1,16 @@
#ifndef _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
-#include "SkTypes.h"
-#include "SkStream.h"
+#include <android/data_space.h>
+#include <jpegrecoverymap/jpegr.h>
+
extern "C" {
#include "jpeglib.h"
#include "jerror.h"
}
+class SkWStream;
+
class YuvToJpegEncoder {
public:
/** Create an encoder based on the YUV format.
@@ -24,7 +27,7 @@ public:
*
* @param stream The jpeg output stream.
* @param inYuv The input yuv data.
- * @param width Width of the the Yuv data in terms of pixels.
+ * @param width Width of the Yuv data in terms of pixels.
* @param height Height of the Yuv data in terms of pixels.
* @param offsets The offsets in each image plane with respect to inYuv.
* @param jpegQuality Picture quality in [0, 100].
@@ -71,4 +74,46 @@ private:
uint8_t* vRows, int rowIndex, int width, int height);
};
+class P010Yuv420ToJpegREncoder {
+public:
+ /** Encode YUV data to jpeg/r, which is output to a stream.
+ * This method will call JpegR::EncodeJPEGR() method. If encoding failed,
+ * Corresponding error code (defined in jpegrerrorcode.h) will be printed and this
+ * method will be terminated and return false.
+ *
+ * @param env JNI environment.
+ * @param stream The jpeg output stream.
+ * @param hdr The input yuv data (p010 format).
+ * @param hdrColorSpaceId color space id for the input hdr.
+ * @param sdr The input yuv data (yuv420p format).
+ * @param sdrColorSpaceId color space id for the input sdr.
+ * @param width Width of the Yuv data in terms of pixels.
+ * @param height Height of the Yuv data in terms of pixels.
+ * @param jpegQuality Picture quality in [0, 100].
+ * @return true if successfully compressed the stream.
+ */
+ bool encode(JNIEnv* env,
+ SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
+ int width, int height, int jpegQuality);
+
+ /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
+ * used in JPEG/R
+ *
+ * @param env JNI environment.
+ * @param aDataSpace data space defined in data_space.h.
+ * @return color gamut for JPEG/R.
+ */
+ static android::jpegrecoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
+
+ /** Map data space (defined in DataSpace.java and data_space.h) to the transfer function
+ * used in JPEG/R
+ *
+ * @param env JNI environment.
+ * @param aDataSpace data space defined in data_space.h.
+ * @return color gamut for JPEG/R.
+ */
+ static android::jpegrecoverymap::jpegr_transfer_function findHdrTransferFunction(
+ JNIEnv* env, int aDataSpace);
+};
+
#endif // _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 132234b38003..8ba750372d18 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -30,12 +30,24 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
-#include "FontUtils.h"
#include "Bitmap.h"
+#include "FontUtils.h"
+#include "SkBitmap.h"
+#include "SkBlendMode.h"
+#include "SkClipOp.h"
+#include "SkColor.h"
+#include "SkColorSpace.h"
#include "SkGraphics.h"
+#include "SkImageInfo.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRRect.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
#include "SkRegion.h"
+#include "SkScalar.h"
#include "SkVertices.h"
-#include "SkRRect.h"
namespace minikin {
class MeasuredText;
@@ -431,6 +443,14 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
blendMode, *paint);
}
+static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
+ jlong paintHandle) {
+ const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle);
+ SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint);
+}
+
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
jlong chunkHandle, jfloat left, jfloat top, jfloat right, jfloat bottom,
jlong paintHandle, jint dstDensity, jint srcDensity) {
@@ -701,9 +721,10 @@ static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) {
}
static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right,
- jfloat bottom, jfloat rx, jfloat ry) {
+ jfloat bottom, jfloat rx, jfloat ry, jfloat alpha) {
auto canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry));
+ canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry),
+ alpha);
}
}; // namespace CanvasJNI
@@ -748,38 +769,38 @@ static const JNINativeMethod gMethods[] = {
// If called from Canvas these are regular JNI
// If called from DisplayListCanvas they are @FastNative
static const JNINativeMethod gDrawMethods[] = {
- {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
- {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong},
- {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
- {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
- {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
- {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
- {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
- {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
- {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
- {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
- {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY},
- {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii},
- {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
- {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
- {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
- {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
- {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
- {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
- {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
- {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
- {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
- {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
- {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
- {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
- {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
- {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
- {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
- {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
- {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
- {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
- {"nPunchHole", "(JFFFFFF)V", (void*) CanvasJNI::punchHole}
-};
+ {"nDrawColor", "(JII)V", (void*)CanvasJNI::drawColor},
+ {"nDrawColor", "(JJJI)V", (void*)CanvasJNI::drawColorLong},
+ {"nDrawPaint", "(JJ)V", (void*)CanvasJNI::drawPaint},
+ {"nDrawPoint", "(JFFJ)V", (void*)CanvasJNI::drawPoint},
+ {"nDrawPoints", "(J[FIIJ)V", (void*)CanvasJNI::drawPoints},
+ {"nDrawLine", "(JFFFFJ)V", (void*)CanvasJNI::drawLine},
+ {"nDrawLines", "(J[FIIJ)V", (void*)CanvasJNI::drawLines},
+ {"nDrawRect", "(JFFFFJ)V", (void*)CanvasJNI::drawRect},
+ {"nDrawRegion", "(JJJ)V", (void*)CanvasJNI::drawRegion},
+ {"nDrawRoundRect", "(JFFFFFFJ)V", (void*)CanvasJNI::drawRoundRect},
+ {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*)CanvasJNI::drawDoubleRoundRectXY},
+ {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*)CanvasJNI::drawDoubleRoundRectRadii},
+ {"nDrawCircle", "(JFFFJ)V", (void*)CanvasJNI::drawCircle},
+ {"nDrawOval", "(JFFFFJ)V", (void*)CanvasJNI::drawOval},
+ {"nDrawArc", "(JFFFFFFZJ)V", (void*)CanvasJNI::drawArc},
+ {"nDrawPath", "(JJJ)V", (void*)CanvasJNI::drawPath},
+ {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+ {"nDrawMesh", "(JJIJ)V", (void*)CanvasJNI::drawMesh},
+ {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
+ {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+ {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+ {"nDrawBitmap", "(JJFFJIII)V", (void*)CanvasJNI::drawBitmap},
+ {"nDrawBitmap", "(JJFFFFFFFFJII)V", (void*)CanvasJNI::drawBitmapRect},
+ {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+ {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
+ {"nDrawText", "(J[CIIFFIJ)V", (void*)CanvasJNI::drawTextChars},
+ {"nDrawText", "(JLjava/lang/String;IIFFIJ)V", (void*)CanvasJNI::drawTextString},
+ {"nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*)CanvasJNI::drawTextRunChars},
+ {"nDrawTextRun", "(JLjava/lang/String;IIIIFFZJ)V", (void*)CanvasJNI::drawTextRunString},
+ {"nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*)CanvasJNI::drawTextOnPathChars},
+ {"nDrawTextOnPath", "(JLjava/lang/String;JFFIJ)V", (void*)CanvasJNI::drawTextOnPathString},
+ {"nPunchHole", "(JFFFFFFF)V", (void*)CanvasJNI::punchHole}};
int register_android_graphics_Canvas(JNIEnv* env) {
int ret = 0;
diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp
index 232fd71a12b4..63d3f83febd6 100644
--- a/libs/hwui/jni/android_graphics_ColorSpace.cpp
+++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp
@@ -18,7 +18,6 @@
#include "SkColor.h"
#include "SkColorSpace.h"
-#include "SkHalf.h"
using namespace android;
@@ -40,15 +39,58 @@ static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) {
///////////////////////////////////////////////////////////////////////////////
+#if defined(__ANDROID__) // __fp16 is not defined on non-Android builds
static float halfToFloat(uint16_t bits) {
-#ifdef __ANDROID__ // __fp16 is not defined on non-Android builds
__fp16 h;
memcpy(&h, &bits, 2);
return (float)h;
+}
#else
- return SkHalfToFloat(bits);
-#endif
+// This is Skia's implementation of SkHalfToFloat, which is
+// based on Fabien Giesen's half_to_float_fast2()
+// see https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/
+static uint16_t halfMantissa(uint16_t h) {
+ return h & 0x03ff;
+}
+
+static uint16_t halfExponent(uint16_t h) {
+ return (h >> 10) & 0x001f;
+}
+
+static uint16_t halfSign(uint16_t h) {
+ return h >> 15;
+}
+
+union FloatUIntUnion {
+ uint32_t mUInt; // this must come first for the initializations below to work
+ float mFloat;
+};
+
+static float halfToFloat(uint16_t bits) {
+ static const FloatUIntUnion magic = { 126 << 23 };
+ FloatUIntUnion o;
+
+ if (halfExponent(bits) == 0) {
+ // Zero / Denormal
+ o.mUInt = magic.mUInt + halfMantissa(bits);
+ o.mFloat -= magic.mFloat;
+ } else {
+ // Set mantissa
+ o.mUInt = halfMantissa(bits) << 13;
+ // Set exponent
+ if (halfExponent(bits) == 0x1f) {
+ // Inf/NaN
+ o.mUInt |= (255 << 23);
+ } else {
+ o.mUInt |= ((127 - 15 + halfExponent(bits)) << 23);
+ }
+ }
+
+ // Set sign
+ o.mUInt |= (halfSign(bits) << 31);
+ return o.mFloat;
}
+#endif // defined(__ANDROID__)
SkColor4f GraphicsJNI::convertColorLong(jlong color) {
if ((color & 0x3f) == 0) {
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
new file mode 100644
index 000000000000..ae22213f4bf4
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "HardwareBufferRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <GraphicsJNI.h>
+#include <RootRenderNode.h>
+#include <TreeInfo.h>
+#include <android-base/unique_fd.h>
+#include <android/native_window.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <renderthread/CanvasContext.h>
+#include <renderthread/RenderProxy.h>
+#include <renderthread/RenderThread.h>
+
+#include "HardwareBufferHelpers.h"
+#include "JvmErrorReporter.h"
+
+namespace android {
+
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+struct {
+ jclass clazz;
+ jmethodID invokeRenderCallback;
+} gHardwareBufferRendererClassInfo;
+
+static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) {
+ if (releaseCallback == nullptr) return nullptr;
+
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto globalCallbackRef =
+ std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(releaseCallback));
+ return [globalCallbackRef](android::base::unique_fd&& fd, int status) {
+ globalCallbackRef->env()->CallStaticVoidMethod(
+ gHardwareBufferRendererClassInfo.clazz,
+ gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(),
+ reinterpret_cast<jint>(fd.release()), reinterpret_cast<jint>(status));
+ };
+}
+
+static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) {
+ auto* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env));
+ node->incStrong(nullptr);
+ node->setName("RootRenderNode");
+ return reinterpret_cast<jlong>(node);
+}
+
+static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject,
+ jlong renderNodePtr) {
+ auto* node = reinterpret_cast<RootRenderNode*>(renderNodePtr);
+ node->destroy();
+}
+
+static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer,
+ jlong renderNodePtr) {
+ auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer);
+ auto* rootRenderNode = reinterpret_cast<RootRenderNode*>(renderNodePtr);
+ ContextFactoryImpl factory(rootRenderNode);
+ auto* proxy = new RenderProxy(true, rootRenderNode, &factory);
+ proxy->setHardwareBuffer(hardwareBuffer);
+ return (jlong)proxy;
+}
+
+static void HardwareBufferRenderer_destroy(jlong renderProxy) {
+ auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
+ delete proxy;
+}
+
+static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
+ auto matrix = SkMatrix();
+ switch (transform) {
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
+ matrix.setRotate(90);
+ matrix.postTranslate(width, 0);
+ break;
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
+ matrix.setRotate(180);
+ matrix.postTranslate(width, height);
+ break;
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
+ matrix.setRotate(270);
+ matrix.postTranslate(0, width);
+ break;
+ default:
+ ALOGE("Invalid transform provided. Transform should be validated from"
+ "the java side. Leveraging identity transform as a fallback");
+ [[fallthrough]];
+ case ANATIVEWINDOW_TRANSFORM_IDENTITY:
+ break;
+ }
+ return matrix;
+}
+
+static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy,
+ jint transform, jint width, jint height,
+ jlong colorspacePtr, jobject consumer) {
+ auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
+ auto skWidth = static_cast<SkScalar>(width);
+ auto skHeight = static_cast<SkScalar>(height);
+ auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
+ auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
+ proxy->setHardwareBufferRenderParams(
+ HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+ nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo())
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ UiFrameInfoBuilder::UNKNOWN_DEADLINE,
+ UiFrameInfoBuilder::UNKNOWN_FRAME_INTERVAL)
+ .addFlag(FrameInfoFlags::SurfaceCanvas);
+ return proxy->syncAndDrawFrame();
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject,
+ jlong renderProxyPtr,
+ jfloat lightX, jfloat lightY,
+ jfloat lightZ,
+ jfloat lightRadius) {
+ auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr);
+ proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius);
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject,
+ jlong renderProxyPtr,
+ jfloat ambientShadowAlpha,
+ jfloat spotShadowAlpha) {
+ auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr);
+ proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
+}
+
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy));
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/graphics/HardwareBufferRenderer";
+
+static const JNINativeMethod gMethods[] = {
+ {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J",
+ (void*)android_graphics_HardwareBufferRenderer_create},
+ {"nRender", "(JIIIJLjava/util/function/Consumer;)I",
+ (void*)android_graphics_HardwareBufferRenderer_render},
+ {"nCreateRootRenderNode", "()J",
+ (void*)android_graphics_HardwareBufferRenderer_createRootNode},
+ {"nSetLightGeometry", "(JFFFF)V",
+ (void*)android_graphics_HardwareBufferRenderer_setLightGeometry},
+ {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha},
+ {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer},
+ {"nDestroyRootRenderNode", "(J)V",
+ (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}};
+
+int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) {
+ jclass hardwareBufferRendererClazz =
+ FindClassOrDie(env, "android/graphics/HardwareBufferRenderer");
+ gHardwareBufferRendererClassInfo.clazz =
+ reinterpret_cast<jclass>(env->NewGlobalRef(hardwareBufferRendererClazz));
+ gHardwareBufferRendererClassInfo.invokeRenderCallback =
+ GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback",
+ "(Ljava/util/function/Consumer;II)V");
+ HardwareBufferHelpers::init();
+ return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index c48448dffdd2..6a7411f5d859 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -23,8 +23,16 @@
#include <Picture.h>
#include <Properties.h>
#include <RootRenderNode.h>
+#include <SkBitmap.h>
+#include <SkColorSpace.h>
+#include <SkData.h>
+#include <SkImage.h>
#include <SkImagePriv.h>
+#include <SkPicture.h>
+#include <SkPixmap.h>
#include <SkSerialProcs.h>
+#include <SkStream.h>
+#include <SkTypeface.h>
#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <inttypes.h>
@@ -48,6 +56,7 @@
#include <atomic>
#include <vector>
+#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
namespace android {
@@ -80,6 +89,11 @@ struct {
jmethodID onFrameComplete;
} gFrameCompleteCallback;
+struct {
+ jmethodID onCopyFinished;
+ jmethodID getDestinationBitmap;
+} gCopyRequest;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -91,20 +105,6 @@ static JNIEnv* getenv(JavaVM* vm) {
typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
ANW_fromSurface fromSurface;
-class JvmErrorReporter : public ErrorHandler {
-public:
- JvmErrorReporter(JNIEnv* env) {
- env->GetJavaVM(&mVm);
- }
-
- virtual void onError(const std::string& message) override {
- JNIEnv* env = getenv(mVm);
- jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
- }
-private:
- JavaVM* mVm;
-};
-
class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> {
public:
explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) {
@@ -242,10 +242,16 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
proxy->setOpaque(opaque);
}
-static void android_view_ThreadedRenderer_setColorMode(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jint colorMode) {
+static jfloat android_view_ThreadedRenderer_setColorMode(JNIEnv* env, jobject clazz, jlong proxyPtr,
+ jint colorMode) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ return proxy->setColorMode(static_cast<ColorMode>(colorMode));
+}
+
+static void android_view_ThreadedRenderer_setTargetSdrHdrRatio(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jfloat ratio) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setColorMode(static_cast<ColorMode>(colorMode));
+ return proxy->setRenderSdrHdrRatio(ratio);
}
static void android_view_ThreadedRenderer_setSdrWhitePoint(JNIEnv* env, jobject clazz,
@@ -258,6 +264,16 @@ static void android_view_ThreadedRenderer_setIsHighEndGfx(JNIEnv* env, jobject c
Properties::setIsHighEndGfx(jIsHighEndGfx);
}
+static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz,
+ jboolean isLowRam) {
+ Properties::setIsLowRam(isLowRam);
+}
+
+static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz,
+ jboolean isSystemOrPersistent) {
+ Properties::setIsSystemOrPersistent(isSystemOrPersistent);
+}
+
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo,
jint frameInfoSize) {
@@ -420,26 +436,6 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec
proxy->forceDrawNextFrame();
}
-class JGlobalRefHolder {
-public:
- JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
-
- virtual ~JGlobalRefHolder() {
- getenv(mVm)->DeleteGlobalRef(mObject);
- mObject = nullptr;
- }
-
- jobject object() { return mObject; }
- JavaVM* vm() { return mVm; }
-
-private:
- JGlobalRefHolder(const JGlobalRefHolder&) = delete;
- void operator=(const JGlobalRefHolder&) = delete;
-
- JavaVM* mVm;
- jobject mObject;
-};
-
using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>;
struct PictureCaptureState {
@@ -451,7 +447,7 @@ struct PictureCaptureState {
};
// TODO: This & Multi-SKP & Single-SKP should all be de-duped into
-// a single "make a SkPicture serailizable-safe" utility somewhere
+// a single "make a SkPicture serializable-safe" utility somewhere
class PictureWrapper : public Picture {
public:
PictureWrapper(sk_sp<SkPicture>&& src, const std::shared_ptr<PictureCaptureState>& state)
@@ -555,10 +551,9 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv*
auto pictureState = std::make_shared<PictureCaptureState>();
proxy->setPictureCapturedCallback([globalCallbackRef,
pictureState](sk_sp<SkPicture>&& picture) {
- JNIEnv* env = getenv(globalCallbackRef->vm());
Picture* wrapper = new PictureWrapper{std::move(picture), pictureState};
- env->CallStaticVoidMethod(gHardwareRenderer.clazz,
- gHardwareRenderer.invokePictureCapturedCallback,
+ globalCallbackRef->env()->CallStaticVoidMethod(
+ gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback,
static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
globalCallbackRef->object());
});
@@ -575,16 +570,14 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback(
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
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());
- jboolean ret = env->CallBooleanMethod(
- globalCallbackRef->object(),
- gASurfaceTransactionCallback.onMergeTransaction,
- static_cast<jlong>(transObj), static_cast<jlong>(scObj),
- static_cast<jlong>(frameNr));
- return ret;
- });
+ proxy->setASurfaceTransactionCallback([globalCallbackRef](int64_t transObj, int64_t scObj,
+ int64_t frameNr) -> bool {
+ jboolean ret = globalCallbackRef->env()->CallBooleanMethod(
+ globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction,
+ static_cast<jlong>(transObj), static_cast<jlong>(scObj),
+ static_cast<jlong>(frameNr));
+ return ret;
+ });
}
}
@@ -599,9 +592,8 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall
auto globalCallbackRef =
std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() {
- JNIEnv* env = getenv(globalCallbackRef->vm());
- env->CallVoidMethod(globalCallbackRef->object(),
- gPrepareSurfaceControlForWebviewCallback.prepare);
+ globalCallbackRef->env()->CallVoidMethod(
+ globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare);
});
}
}
@@ -618,7 +610,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
env->NewGlobalRef(frameCallback));
proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
int64_t frameNr) -> std::function<void(bool)> {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = globalCallbackRef->env();
ScopedLocalRef<jobject> frameCommitCallback(
env, env->CallObjectMethod(
globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
@@ -657,22 +649,45 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env,
auto globalCallbackRef =
std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
proxy->setFrameCompleteCallback([globalCallbackRef]() {
- JNIEnv* env = getenv(globalCallbackRef->vm());
- env->CallVoidMethod(globalCallbackRef->object(),
- gFrameCompleteCallback.onFrameComplete);
+ globalCallbackRef->env()->CallVoidMethod(globalCallbackRef->object(),
+ gFrameCompleteCallback.onFrameComplete);
});
}
}
-static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
- jobject clazz, jobject jsurface, jint left, jint top,
- jint right, jint bottom, jlong bitmapPtr) {
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+class CopyRequestAdapter : public CopyRequest {
+public:
+ CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect)
+ : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ jlong bitmapPtr = mRefHolder.env()->CallLongMethod(
+ mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
+ SkBitmap bitmap;
+ bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ mRefHolder.env()->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
+ static_cast<jint>(result));
+ }
+
+private:
+ JGlobalRefHolder mRefHolder;
+};
+
+static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz,
+ jobject jsurface, jint left, jint top,
+ jint right, jint bottom,
+ jobject jCopyRequest) {
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto copyRequest = std::make_shared<CopyRequestAdapter>(vm, env->NewGlobalRef(jCopyRequest),
+ Rect(left, top, right, bottom));
ANativeWindow* window = fromSurface(env, jsurface);
- jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
+ RenderProxy::copySurfaceInto(window, std::move(copyRequest));
ANativeWindow_release(window);
- return result;
}
class ContextFactory : public IContextFactory {
@@ -811,6 +826,16 @@ static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jo
RenderProxy::setRtAnimationsEnabled(enabled);
}
+static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->notifyCallbackPending();
+}
+
+static void android_view_ThreadedRenderer_notifyExpensiveFrame(JNIEnv*, jclass, jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->notifyExpensiveFrame();
+}
+
// Plumbs the display density down to DeviceInfo.
static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
// Convert from dpi to density-independent pixels.
@@ -818,17 +843,18 @@ static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass,
DeviceInfo::setDensity(density);
}
-static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth,
- jint physicalHeight, jfloat refreshRate,
- jint wideColorDataspace,
- jlong appVsyncOffsetNanos,
- jlong presentationDeadlineNanos) {
+static void android_view_ThreadedRenderer_initDisplayInfo(
+ JNIEnv* env, jclass, jint physicalWidth, jint physicalHeight, jfloat refreshRate,
+ jint wideColorDataspace, jlong appVsyncOffsetNanos, jlong presentationDeadlineNanos,
+ jboolean supportFp16ForHdr, jboolean supportMixedColorSpaces) {
DeviceInfo::setWidth(physicalWidth);
DeviceInfo::setHeight(physicalHeight);
DeviceInfo::setRefreshRate(refreshRate);
DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace));
DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
+ DeviceInfo::setSupportFp16ForHdr(supportFp16ForHdr);
+ DeviceInfo::setSupportMixedColorSpaces(supportMixedColorSpaces);
}
static void android_view_ThreadedRenderer_setDrawingEnabled(JNIEnv*, jclass, jboolean enabled) {
@@ -907,9 +933,14 @@ static const JNINativeMethod gMethods[] = {
{"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha},
{"nSetLightGeometry", "(JFFFF)V", (void*)android_view_ThreadedRenderer_setLightGeometry},
{"nSetOpaque", "(JZ)V", (void*)android_view_ThreadedRenderer_setOpaque},
- {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
+ {"nSetColorMode", "(JI)F", (void*)android_view_ThreadedRenderer_setColorMode},
+ {"nSetTargetSdrHdrRatio", "(JF)V",
+ (void*)android_view_ThreadedRenderer_setTargetSdrHdrRatio},
{"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
{"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx},
+ {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam},
+ {"nSetIsSystemOrPersistent", "(Z)V",
+ (void*)android_view_ThreadedRenderer_setIsSystemOrPersistent},
{"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame},
{"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy},
{"nRegisterAnimatingRenderNode", "(JJ)V",
@@ -961,7 +992,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback},
{"nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver},
{"nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver},
- {"nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
+ {"nCopySurfaceInto",
+ "(Landroid/view/Surface;IIIILandroid/graphics/HardwareRenderer$CopyRequest;)V",
(void*)android_view_ThreadedRenderer_copySurfaceInto},
{"nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
(void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode},
@@ -974,7 +1006,7 @@ static const JNINativeMethod gMethods[] = {
{"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
- {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+ {"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
{"isWebViewOverlaysEnabled", "()Z",
(void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
@@ -982,6 +1014,10 @@ static const JNINativeMethod gMethods[] = {
{"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled},
{"nSetRtAnimationsEnabled", "(Z)V",
(void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
+ {"nNotifyCallbackPending", "(J)V",
+ (void*)android_view_ThreadedRenderer_notifyCallbackPending},
+ {"nNotifyExpensiveFrame", "(J)V",
+ (void*)android_view_ThreadedRenderer_notifyExpensiveFrame},
};
static JavaVM* mJvm = nullptr;
@@ -1034,6 +1070,11 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
gFrameCompleteCallback.onFrameComplete =
GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V");
+ jclass copyRequest = FindClassOrDie(env, "android/graphics/HardwareRenderer$CopyRequest");
+ gCopyRequest.onCopyFinished = GetMethodIDOrDie(env, copyRequest, "onCopyFinished", "(I)V");
+ gCopyRequest.getDestinationBitmap =
+ GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
+
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index cf6702e45fff..ca667b0d09bc 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -23,8 +23,6 @@ namespace android {
static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), "
"update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here");
-static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, "
- "only float scalar is supported");
class SkMatrixGlue {
public:
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
new file mode 100644
index 000000000000..5cb43e54e499
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <Mesh.h>
+#include <SkMesh.h>
+#include <jni.h>
+
+#include <utility>
+
+#include "BufferUtils.h"
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+namespace android {
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
+ jfloat right, jfloat bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ size_t bufferSize = vertexCount * skMeshSpec->stride();
+ auto buffer = copyJavaNioBufferToVector(env, vertexBuffer, bufferSize, isDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
+ std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto [valid, msg] = meshPtr->validate();
+ if (!valid) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+ }
+ return reinterpret_cast<jlong>(meshPtr);
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+ jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+ jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+ jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
+ auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+ auto vertexBufferSize = vertexCount * skMeshSpec->stride();
+ auto indexBufferSize = indexCount * gIndexByteSize;
+ auto vBuf = copyJavaNioBufferToVector(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
+ auto iBuf = copyJavaNioBufferToVector(env, indexBuffer, indexBufferSize, isIndexDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
+ auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+ auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
+ std::move(iBuf), indexCount, indexOffset,
+ std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto [valid, msg] = meshPtr->validate();
+ if (!valid) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+ }
+
+ return reinterpret_cast<jlong>(meshPtr);
+}
+
+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 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 nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const float values[], int count,
+ bool isColor) {
+ MeshUniformBuilder::MeshUniform 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 updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+ jint count) {
+ auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
+ wrapper->markDirty();
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
+ jfloatArray jvalues, jboolean isColor) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, jUniformName);
+ AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+ nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+ autoValues.length(), isColor);
+ wrapper->markDirty();
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+ const char* uniformName, const int values[], int count) {
+ MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ 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) * count);
+ }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jint value1, jint value2, jint value3, jint value4, jint count) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
+ wrapper->markDirty();
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+ jintArray values) {
+ auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaIntArray autoValues(env, values, 0);
+ nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+ autoValues.length());
+ wrapper->markDirty();
+}
+
+static void MeshWrapper_destroy(Mesh* wrapper) {
+ delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+ {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
+ {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
+ (void*)makeIndexed},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+ return 0;
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index d78df3dca3b1..1af60b2f5fae 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -22,7 +22,10 @@
#include "SkFont.h"
#include "SkFontMetrics.h"
#include "SkFontMgr.h"
+#include "SkRect.h"
#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkStream.h"
#include "SkTypeface.h"
#include "GraphicsJNI.h"
#include <nativehelper/ScopedUtfChars.h>
@@ -226,7 +229,7 @@ static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) {
static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
std::string path = std::string(reader.readString());
if (path.empty()) {
return nullptr;
@@ -268,7 +271,7 @@ static jint Font_getPackedStyle(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
return reader.read<int>();
} else {
@@ -281,7 +284,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip<int>(); // fontIndex
return reader.readArray<minikin::FontVariation>().second;
@@ -296,7 +299,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
minikin::FontVariation var;
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip<int>(); // fontIndex
var = reader.readArray<minikin::FontVariation>().first[index];
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index b68213549938..897c4d71c0d5 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -57,7 +57,8 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
- jstring langTags, jint variant, jboolean isCustomFallback) {
+ jstring langTags, jint variant, jboolean isCustomFallback,
+ jboolean isDefaultFallback) {
std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -66,9 +67,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP
ScopedUtfChars str(env, langTags);
localeId = minikin::registerLocaleList(str.c_str());
}
- std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
+ std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
- isCustomFallback);
+ isCustomFallback, isDefaultFallback);
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -116,10 +117,10 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
- { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder },
- { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont },
- { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build },
- { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
+ {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
+ {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
+ {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
static const JNINativeMethod gFontFamilyMethods[] = {
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
new file mode 100644
index 000000000000..55f03bd9f7b1
--- /dev/null
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "GraphemeBreaker"
+
+#include <minikin/GraphemeBreak.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include "GraphicsJNI.h"
+
+namespace android {
+
+static void nIsGraphemeBreak(JNIEnv* env, jclass, jfloatArray advances, jcharArray text, jint start,
+ jint end, jbooleanArray isGraphemeBreak) {
+ if (start > end || env->GetArrayLength(advances) < end ||
+ env->GetArrayLength(isGraphemeBreak) < end - start) {
+ doThrowAIOOBE(env);
+ }
+
+ if (start == end) {
+ return;
+ }
+
+ ScopedFloatArrayRO advancesArray(env, advances);
+ ScopedCharArrayRO textArray(env, text);
+ ScopedBooleanArrayRW isGraphemeBreakArray(env, isGraphemeBreak);
+
+ size_t count = end - start;
+ for (size_t offset = 0; offset < count; ++offset) {
+ bool isBreak = minikin::GraphemeBreak::isGraphemeBreak(advancesArray.get(), textArray.get(),
+ start, end, start + offset);
+ isGraphemeBreakArray[offset] = isBreak ? JNI_TRUE : JNI_FALSE;
+ }
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nIsGraphemeBreak",
+ "("
+ "[F" // advances
+ "[C" // text
+ "I" // start
+ "I" // end
+ "[Z" // isGraphemeBreak
+ ")V",
+ (void*)nIsGraphemeBreak},
+};
+
+int register_android_graphics_text_GraphemeBreak(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/text/GraphemeBreak", gMethods,
+ NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 3f89c0712407..6a052dbb7cea 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -19,6 +19,8 @@
#include "RenderNode.h"
#include "SkiaDisplayList.h"
+class SkRRect;
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index dc72aead4873..a4960ea17c79 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -24,6 +24,7 @@
#include "SkClipStack.h"
#include "SkRect.h"
#include "SkM44.h"
+#include "include/gpu/GpuTypes.h" // from Skia
#include "utils/GLUtils.h"
namespace android {
@@ -92,7 +93,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
SkImageInfo surfaceInfo =
canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
tmpSurface =
- SkSurface::MakeRenderTarget(directContext, SkBudgeted::kYes, surfaceInfo);
+ SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
GrGLFramebufferInfo fboInfo;
diff --git a/libs/hwui/pipeline/skia/HolePunch.h b/libs/hwui/pipeline/skia/HolePunch.h
index 92c6f7721a08..d0e1ca35049a 100644
--- a/libs/hwui/pipeline/skia/HolePunch.h
+++ b/libs/hwui/pipeline/skia/HolePunch.h
@@ -17,7 +17,6 @@
#pragma once
#include <string>
-#include "SkRRect.h"
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3ba540921f64..99f54c19d2e5 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -25,6 +25,7 @@
#include "SkColorFilter.h"
#include "SkRuntimeEffect.h"
#include "SkSurface.h"
+#include "Tonemapper.h"
#include "gl/GrGLTypes.h"
#include "math/mat4.h"
#include "system/graphics-base-v1.0.h"
@@ -76,37 +77,6 @@ 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;
-}
-
static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
// Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
// a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
@@ -215,31 +185,10 @@ bool LayerDrawable::DrawLayer(GrRecordingContext* context,
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 {
- canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- constraint);
- }
+ tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(),
+ paint);
+ canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+ constraint);
canvas->restore();
// restore the original matrix
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9e17b9e6d985..1a47db5c8ec2 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -15,7 +15,11 @@
*/
#include "RenderNodeDrawable.h"
+#include <SkPaint.h>
#include <SkPaintFilterCanvas.h>
+#include <SkPoint.h>
+#include <SkRRect.h>
+#include <SkRect.h>
#include <gui/TraceUtils.h>
#include "RenderNode.h"
#include "SkiaDisplayList.h"
@@ -197,6 +201,7 @@ protected:
paint.setAlpha((uint8_t)paint.getAlpha() * mAlpha);
return true;
}
+
void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
// We unroll the drawable using "this" canvas, so that draw calls contained inside will
// get their alpha applied. The default SkPaintFilterCanvas::onDrawDrawable does not unroll.
@@ -288,7 +293,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
// with the same canvas transformation + clip into the target
// canvas then draw the layer on top
if (renderNode->hasHolePunches()) {
- TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
+ TransformCanvas transformCanvas(canvas, SkBlendMode::kDstOut);
displayList->draw(&transformCanvas);
}
canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 6c390c3fce24..c7582e734009 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -18,6 +18,7 @@
#include "SkiaUtils.h"
+#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkDrawable.h>
#include <SkMatrix.h>
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 7cfccb56382c..11977bd54c2c 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -19,8 +19,15 @@
#include "SkiaDisplayList.h"
#include "LightingInfo.h"
+#include <SkColor.h>
+#include <SkMatrix.h>
+#include <SkPath.h>
#include <SkPathOps.h>
+#include <SkPoint3.h>
+#include <SkRect.h>
+#include <SkScalar.h>
#include <SkShadowUtils.h>
+#include <include/private/SkShadowFlags.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 2ab7a58556a2..00919dc3f22a 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -16,6 +16,7 @@
#include "ShaderCache.h"
#include <GrDirectContext.h>
+#include <SkData.h>
#include <gui/TraceUtils.h>
#include <log/log.h>
#include <openssl/sha.h>
@@ -32,7 +33,8 @@ namespace skiapipeline {
// Cache size limits.
static const size_t maxKeySize = 1024;
static const size_t maxValueSize = 2 * 1024 * 1024;
-static const size_t maxTotalSize = 1024 * 1024;
+static const size_t maxTotalSize = 4 * 1024 * 1024;
+static_assert(maxKeySize + maxValueSize < maxTotalSize);
ShaderCache::ShaderCache() {
// There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header.
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 4e3eb816da29..f5506d60f811 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -17,12 +17,15 @@
#pragma once
#include <GrContextOptions.h>
+#include <SkRefCnt.h>
#include <cutils/compiler.h>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
+class SkData;
+
namespace android {
class BlobCache;
@@ -45,7 +48,7 @@ public:
* and puts the ShaderCache into an initialized state, such that it is
* able to insert and retrieve entries from the cache. If identity is
* non-null and validation fails, the cache is initialized but contains
- * no data. If size is less than zero, the cache is initilaized but
+ * no data. If size is less than zero, the cache is initialized but
* contains no data.
*
* This should be called when HWUI pipeline is initialized. When not in
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index fcfc4f82abed..af2d3b34bac7 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -23,6 +23,7 @@
#else
#include "DamageAccumulator.h"
#endif
+#include "TreeInfo.h"
#include "VectorDrawable.h"
#ifdef __ANDROID__
#include "renderthread/CanvasContext.h"
@@ -102,6 +103,12 @@ bool SkiaDisplayList::prepareListAndChildren(
info.prepareTextures = false;
info.canvasContext.unpinImages();
}
+
+ auto grContext = info.canvasContext.getGrContext();
+ for (auto mesh : mMeshes) {
+ mesh->updateSkMesh(grContext);
+ }
+
#endif
bool hasBackwardProjectedNodesHere = false;
@@ -168,6 +175,7 @@ void SkiaDisplayList::reset() {
mDisplayList.reset();
+ mMeshes.clear();
mMutableImages.clear();
mVectorDrawables.clear();
mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 2a677344b7b2..7af31a4dc4c6 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -18,6 +18,7 @@
#include <deque>
+#include "Mesh.h"
#include "RecordingCanvas.h"
#include "RenderNodeDrawable.h"
#include "TreeInfo.h"
@@ -167,6 +168,7 @@ public:
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
+ std::vector<const Mesh*> mMeshes;
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 8e350d5012a5..202a62cf320c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -55,7 +55,9 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
- if (!isSurfaceReady() && mNativeWindow) {
+ if (mHardwareBuffer) {
+ mRenderThread.requireGlContext();
+ } else if (!isSurfaceReady() && mNativeWindow) {
setSurface(mNativeWindow.get(), mSwapBehavior);
}
@@ -67,17 +69,24 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
}
Frame SkiaOpenGLPipeline::getFrame() {
- LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
- "drawRenderNode called on a context with no surface!");
- return mEglManager.beginFrame(mEglSurface);
+ if (mHardwareBuffer) {
+ AHardwareBuffer_Desc description;
+ AHardwareBuffer_describe(mHardwareBuffer, &description);
+ return Frame(description.width, description.height, 0);
+ } else {
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no surface!");
+ return mEglManager.beginFrame(mEglSurface);
+ }
}
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) {
- if (!isCapturingSkp()) {
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) {
+ if (!isCapturingSkp() && !mHardwareBuffer) {
mEglManager.damageFrame(frame, dirty);
}
@@ -104,19 +113,31 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
- mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
- mSurfaceColorSpace, &props));
+ sk_sp<SkSurface> surface;
+ SkMatrix preTransform;
+ if (mHardwareBuffer) {
+ surface = getBufferSkSurface(bufferParams);
+ preTransform = bufferParams.getTransform();
+ } else {
+ surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+ getSurfaceOrigin(), colorType,
+ mSurfaceColorSpace, &props);
+ preTransform = SkMatrix::I();
+ }
- LightingInfo::updateLighting(lightGeometry, lightInfo);
+ SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
+ LightGeometry localGeometry = lightGeometry;
+ localGeometry.center.x = lightCenter.fX;
+ localGeometry.center.y = lightCenter.fY;
+ LightingInfo::updateLighting(localGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
- SkMatrix::I());
+ preTransform);
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = surface->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
@@ -142,6 +163,10 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
+ if (mHardwareBuffer) {
+ return false;
+ }
+
*requireSwap = drew || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
@@ -197,6 +222,26 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
return false;
}
+[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() {
+ int fence = -1;
+ EGLSyncKHR sync = EGL_NO_SYNC_KHR;
+ mEglManager.createReleaseFence(true, &sync, &fence);
+ // If a sync object is returned here then the device does not support native
+ // fences, we block on the returned sync and return -1 as a file descriptor
+ if (sync != EGL_NO_SYNC_KHR) {
+ EGLDisplay display = mEglManager.eglDisplay();
+ EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000);
+ if (result == EGL_FALSE) {
+ ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x",
+ eglGetError());
+ } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+ ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence");
+ }
+ eglDestroySyncKHR(display, sync);
+ }
+ return android::base::unique_fd(fence);
+}
+
bool SkiaOpenGLPipeline::isSurfaceReady() {
return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index a80c613697f2..940d6bfdb83c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -21,6 +21,7 @@
#include "SkiaPipeline.h"
#include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
namespace android {
@@ -36,19 +37,18 @@ public:
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
- renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
- const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque,
- const LightInfo& lightInfo,
- const std::vector<sp<RenderNode> >& renderNodes,
- FrameInfoVisualizer* profiler) override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+ [[nodiscard]] android::base::unique_fd flush() override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index bc386feb2d6f..6628463bcabf 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,16 +16,27 @@
#include "SkiaPipeline.h"
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkColorSpace.h>
+#include <SkData.h>
+#include <SkImage.h>
#include <SkImageEncoder.h>
#include <SkImageInfo.h>
#include <SkImagePriv.h>
+#include <SkMatrix.h>
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
#include <SkSerialProcs.h>
+#include <SkStream.h>
+#include <SkString.h>
#include <SkTypeface.h>
+#include "include/gpu/GpuTypes.h" // from Skia
#include <android-base/properties.h>
#include <unistd.h>
@@ -177,7 +188,7 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- SkBudgeted::kYes, info, 0,
+ skgpu::Budgeted::kYes, info, 0,
this->getSurfaceOrigin(), &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
@@ -594,6 +605,31 @@ void SkiaPipeline::dumpResourceCacheUsage() const {
ALOGD("%s", log.c_str());
}
+void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
@@ -606,12 +642,14 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
break;
case ColorMode::Hdr:
- mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020);
+ mSurfaceColorType = SkColorType::kN32_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
break;
case ColorMode::Hdr10:
mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020);
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
break;
case ColorMode::A8:
mSurfaceColorType = SkColorType::kAlpha_8_SkColorType;
@@ -620,6 +658,16 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
}
}
+void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
+ mTargetSdrHdrRatio = ratio;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
+ SkNamedGamut::kDisplayP3);
+ } else {
+ mTargetSdrHdrRatio = 1.f;
+ }
+}
+
// Overdraw debugging
// These colors should be kept in sync with Caches::getOverdrawColor() with a few differences.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index bc8a5659dd83..befee8989383 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -16,14 +16,18 @@
#pragma once
-#include <SkSurface.h>
+#include <SkColorSpace.h>
#include <SkDocument.h>
#include <SkMultiPictureDocument.h>
+#include <SkSurface.h>
+
#include "Lighting.h"
#include "hwui/AnimatedImageDrawable.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/IRenderPipeline.h"
+class SkFILEWStream;
class SkPictureRecorder;
struct SkSharingSerialContext;
@@ -71,14 +75,26 @@ public:
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
+ virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
+ void setTargetSdrHdrRatio(float ratio) override;
+
protected:
+ sk_sp<SkSurface> getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
void dumpResourceCacheUsage() const;
renderthread::RenderThread& mRenderThread;
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+ sk_sp<SkSurface> mBufferSurface = nullptr;
+ sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
+
ColorMode mColorMode = ColorMode::Default;
SkColorType mSurfaceColorType;
sk_sp<SkColorSpace> mSurfaceColorSpace;
+ float mTargetSdrHdrRatio = 1.f;
bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
index 492c39f1288c..81cfc5d93419 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
@@ -33,13 +33,5 @@ void SkiaProfileRenderer::drawRects(const float* rects, int count, const SkPaint
}
}
-uint32_t SkiaProfileRenderer::getViewportWidth() {
- return mCanvas->imageInfo().width();
-}
-
-uint32_t SkiaProfileRenderer::getViewportHeight() {
- return mCanvas->imageInfo().height();
-}
-
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
index dc8420f4e01b..96d2a5e58139 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
@@ -23,18 +23,21 @@ namespace uirenderer {
class SkiaProfileRenderer : public IProfileRenderer {
public:
- explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {}
+ explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height)
+ : mCanvas(canvas), mWidth(width), mHeight(height) {}
void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
void drawRects(const float* rects, int count, const SkPaint& paint) override;
- uint32_t getViewportWidth() override;
- uint32_t getViewportHeight() override;
+ uint32_t getViewportWidth() override { return mWidth; }
+ uint32_t getViewportHeight() override { return mHeight; }
virtual ~SkiaProfileRenderer() {}
private:
// Does not have ownership.
SkCanvas* mCanvas;
+ uint32_t mWidth;
+ uint32_t mHeight;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 9c51e628e04a..3ca7eeb37a89 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -16,7 +16,19 @@
#include "SkiaRecordingCanvas.h"
#include "hwui/Paint.h"
+#include <SkBlendMode.h>
+#include <SkData.h>
+#include <SkDrawable.h>
+#include <SkImage.h>
#include <SkImagePriv.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkRRect.h>
+#include <SkSamplingOptions.h>
+#include <SkTypes.h>
#include "CanvasTransform.h"
#ifdef __ANDROID__ // Layoutlib does not support Layers
#include "Layer.h"
@@ -30,6 +42,8 @@
#include "pipeline/skia/VkFunctorDrawable.h"
#include "pipeline/skia/VkInteropFunctorDrawable.h"
#endif
+#include <log/log.h>
+#include <ui/FatVector.h>
namespace android {
namespace uirenderer {
@@ -42,7 +56,7 @@ namespace skiapipeline {
void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
int height) {
mCurrentBarrier = nullptr;
- SkASSERT(mDisplayList.get() == nullptr);
+ LOG_FATAL_IF(mDisplayList.get() != nullptr);
if (renderNode) {
mDisplayList = renderNode->detachAvailableList();
@@ -56,20 +70,22 @@ void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, in
mDisplayList->setHasHolePunches(false);
}
-void SkiaRecordingCanvas::punchHole(const SkRRect& rect) {
- // Add the marker annotation to allow HWUI to determine where the current
- // clip/transformation should be applied
+void SkiaRecordingCanvas::punchHole(const SkRRect& rect, float alpha) {
+ // Add the marker annotation to allow HWUI to determine the current
+ // clip/transformation and alpha should be applied
SkVector vector = rect.getSimpleRadii();
- float data[2];
+ float data[3];
data[0] = vector.x();
data[1] = vector.y();
+ data[2] = alpha;
mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(),
- SkData::MakeWithCopy(data, 2 * sizeof(float)));
+ SkData::MakeWithCopy(data, sizeof(data)));
// Clear the current rect within the layer itself
SkPaint paint = SkPaint();
- paint.setColor(0);
- paint.setBlendMode(SkBlendMode::kClear);
+ paint.setColor(SkColors::kBlack);
+ paint.setAlphaf(alpha);
+ paint.setBlendMode(SkBlendMode::kDstOut);
mRecorder.drawRRect(rect, paint);
mDisplayList->setHasHolePunches(true);
@@ -192,40 +208,52 @@ void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) {
}
}
+void SkiaRecordingCanvas::handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload) {
+ // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
+ // it is not safe to store a raw SkImage pointer, because the image object will be destroyed
+ // when this function ends.
+ if (!bitmap.isImmutable() && payload.image.get() && !payload.image->unique()) {
+ mDisplayList->mMutableImages.push_back(payload.image.get());
+ }
+
+ if (bitmap.hasGainmap()) {
+ auto gainmapBitmap = bitmap.gainmap()->bitmap;
+ // Not all DrawImagePayload receivers will store the gainmap (such as DrawImageLattice),
+ // so only store it in the mutable list if it was actually recorded
+ if (!gainmapBitmap->isImmutable() && payload.gainmapImage.get() &&
+ !payload.gainmapImage->unique()) {
+ mDisplayList->mMutableImages.push_back(payload.gainmapImage.get());
+ }
+ }
+}
+
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
- sk_sp<SkImage> image = bitmap.makeImage();
+ auto payload = DrawImagePayload(bitmap);
applyLooper(
paint,
[&](const Paint& p) {
- mRecorder.drawImage(image, left, top, p.sampling(), &p, bitmap.palette());
+ mRecorder.drawImage(DrawImagePayload(payload), left, top, p.sampling(), &p);
},
FilterForImage);
- // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
- // it is not safe to store a raw SkImage pointer, because the image object will be destroyed
- // when this function ends.
- if (!bitmap.isImmutable() && image.get() && !image->unique()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ handleMutableImages(bitmap, payload);
}
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) {
SkAutoCanvasRestore acr(&mRecorder, true);
concat(matrix);
- sk_sp<SkImage> image = bitmap.makeImage();
+ auto payload = DrawImagePayload(bitmap);
applyLooper(
paint,
[&](const Paint& p) {
- mRecorder.drawImage(image, 0, 0, p.sampling(), &p, bitmap.palette());
+ mRecorder.drawImage(DrawImagePayload(payload), 0, 0, p.sampling(), &p);
},
FilterForImage);
- if (!bitmap.isImmutable() && image.get() && !image->unique()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ handleMutableImages(bitmap, payload);
}
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -234,20 +262,17 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImage> image = bitmap.makeImage();
+ auto payload = DrawImagePayload(bitmap);
applyLooper(
paint,
[&](const Paint& p) {
- mRecorder.drawImageRect(image, srcRect, dstRect, p.sampling(), &p,
- SkCanvas::kFast_SrcRectConstraint, bitmap.palette());
+ mRecorder.drawImageRect(DrawImagePayload(payload), srcRect, dstRect, p.sampling(),
+ &p, SkCanvas::kFast_SrcRectConstraint);
},
FilterForImage);
- if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() &&
- !dstRect.isEmpty()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ handleMutableImages(bitmap, payload);
}
void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft,
@@ -265,15 +290,17 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch
numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
}
- SkAutoSTMalloc<25, SkCanvas::Lattice::RectType> flags(numFlags);
- SkAutoSTMalloc<25, SkColor> colors(numFlags);
+ // Most times, we do not have very many flags/colors, so the stack allocated part of
+ // FatVector will save us a heap allocation.
+ FatVector<SkCanvas::Lattice::RectType, 25> flags(numFlags);
+ FatVector<SkColor, 25> colors(numFlags);
if (numFlags > 0) {
- NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk, colors.get());
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.data(), numFlags, chunk, colors.data());
}
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImage> image = bitmap.makeImage();
+ auto payload = DrawImagePayload(bitmap);
// HWUI always draws 9-patches with linear filtering, regardless of the Paint.
const SkFilterMode filter = SkFilterMode::kLinear;
@@ -281,13 +308,11 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch
applyLooper(
paint,
[&](const SkPaint& p) {
- mRecorder.drawImageLattice(image, lattice, dst, filter, &p, bitmap.palette());
+ mRecorder.drawImageLattice(DrawImagePayload(payload), lattice, dst, filter, &p);
},
FilterForImage);
- if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ handleMutableImages(bitmap, payload);
}
double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedImage) {
@@ -296,6 +321,11 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma
return 0;
}
+void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+ mDisplayList->mMeshes.push_back(&mesh);
+ mRecorder.drawMesh(mesh, blender, paint);
+}
+
} // namespace skiapipeline
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 1445a27e4248..a8e4580dc200 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -22,6 +22,11 @@
#include "SkiaDisplayList.h"
#include "pipeline/skia/AnimatedDrawables.h"
+class SkBitmap;
+class SkMatrix;
+class SkPaint;
+class SkRRect;
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -45,7 +50,7 @@ public:
initDisplayList(renderNode, width, height);
}
- virtual void punchHole(const SkRRect& rect) override;
+ virtual void punchHole(const SkRRect& rect, float alpha) override;
virtual void finishRecording(uirenderer::RenderNode* destination) override;
std::unique_ptr<SkiaDisplayList> finishRecording();
@@ -76,6 +81,7 @@ public:
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
virtual void enableZ(bool enableZ) override;
+ virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
@@ -97,6 +103,8 @@ private:
*/
void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
+ void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
+
using INHERITED = SkiaCanvas;
};
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index cc2565d88d5e..99298bc0fe9b 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -57,43 +57,63 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() {
MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
- if (!isSurfaceReady() && mNativeWindow) {
+ if (mHardwareBuffer) {
+ mRenderThread.requireVkContext();
+ } else if (!isSurfaceReady() && mNativeWindow) {
setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default);
}
return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
}
Frame SkiaVulkanPipeline::getFrame() {
- LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
- return vulkanManager().dequeueNextBuffer(mVkSurface);
+ if (mHardwareBuffer) {
+ AHardwareBuffer_Desc description;
+ AHardwareBuffer_describe(mHardwareBuffer, &description);
+ return Frame(description.width, description.height, 0);
+ } else {
+ LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
+ "getFrame() called on a context with no surface!");
+ return vulkanManager().dequeueNextBuffer(mVkSurface);
+ }
}
IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) {
- sk_sp<SkSurface> backBuffer = mVkSurface->getCurrentSkSurface();
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) {
+ sk_sp<SkSurface> backBuffer;
+ SkMatrix preTransform;
+ if (mHardwareBuffer) {
+ backBuffer = getBufferSkSurface(bufferParams);
+ preTransform = bufferParams.getTransform();
+ } else {
+ backBuffer = mVkSurface->getCurrentSkSurface();
+ preTransform = mVkSurface->getCurrentPreTransform();
+ }
+
if (backBuffer.get() == nullptr) {
return {false, -1};
}
// update the coordinates of the global light position based on surface rotation
- SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x,
- lightGeometry.center.y);
+ SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
LightGeometry localGeometry = lightGeometry;
localGeometry.center.x = lightCenter.fX;
localGeometry.center.y = lightCenter.fY;
LightingInfo::updateLighting(localGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer,
- mVkSurface->getCurrentPreTransform());
+ preTransform);
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = backBuffer->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkAutoCanvasRestore saver(profileCanvas, true);
+ profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
@@ -114,12 +134,16 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) {
- *requireSwap = drew;
-
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
+ if (mHardwareBuffer) {
+ return false;
+ }
+
+ *requireSwap = drew;
+
if (*requireSwap) {
vulkanManager().swapBuffers(mVkSurface, screenDirty);
}
@@ -135,6 +159,12 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() {
void SkiaVulkanPipeline::onStop() {}
+[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() {
+ int fence = -1;
+ vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext());
+ return android::base::unique_fd(fence);
+}
+
// We can safely ignore the swap behavior because VkManager will always operate
// in a mode equivalent to EGLManager::SwapBehavior::kBufferAge
bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) {
@@ -155,6 +185,13 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapB
return mVkSurface != nullptr;
}
+void SkiaVulkanPipeline::setTargetSdrHdrRatio(float ratio) {
+ SkiaPipeline::setTargetSdrHdrRatio(ratio);
+ if (mVkSurface) {
+ mVkSurface->setColorSpace(mSurfaceColorSpace);
+ }
+}
+
bool SkiaVulkanPipeline::isSurfaceReady() {
return CC_UNLIKELY(mVkSurface != nullptr);
}
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index a6e685d08aeb..d921ddb0d0fb 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -16,11 +16,15 @@
#pragma once
+#include "SkRefCnt.h"
#include "SkiaPipeline.h"
+#include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
#include "renderthread/VulkanSurface.h"
-#include "renderstate/RenderState.h"
+class SkBitmap;
+struct SkRect;
namespace android {
namespace uirenderer {
@@ -33,22 +37,24 @@ public:
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
- renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
- const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque,
- const LightInfo& lightInfo,
- const std::vector<sp<RenderNode> >& renderNodes,
- FrameInfoVisualizer* profiler) override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
+ [[nodiscard]] android::base::unique_fd flush() override;
+
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
+ bool supportsExtendedRangeHdr() const override { return true; }
+ void setTargetSdrHdrRatio(float ratio) override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
@@ -59,7 +65,6 @@ protected:
private:
renderthread::VulkanManager& vulkanManager();
-
renderthread::VulkanSurface* mVkSurface = nullptr;
sp<ANativeWindow> mNativeWindow;
};
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index 2dbeb3adfab3..cad3703d8d2b 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -14,8 +14,12 @@
* limitations under the License.
*/
#include "StretchMask.h"
-#include "SkSurface.h"
+
+#include "SkBlendMode.h"
#include "SkCanvas.h"
+#include "SkSurface.h"
+#include "include/gpu/GpuTypes.h" // from Skia
+
#include "TransformCanvas.h"
#include "SkiaDisplayList.h"
@@ -34,7 +38,7 @@ void StretchMask::draw(GrRecordingContext* context,
// not match.
mMaskSurface = SkSurface::MakeRenderTarget(
context,
- SkBudgeted::kYes,
+ skgpu::Budgeted::kYes,
SkImageInfo::Make(
width,
height,
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index 41e36874b862..c320df035d08 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -19,19 +19,25 @@
#include "HolePunch.h"
#include "SkData.h"
#include "SkDrawable.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkRect.h"
+#include "SkRRect.h"
using namespace android::uirenderer::skiapipeline;
void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) {
if (HOLE_PUNCH_ANNOTATION == key) {
auto* rectParams = reinterpret_cast<const float*>(value->data());
- float radiusX = rectParams[0];
- float radiusY = rectParams[1];
+ const float radiusX = rectParams[0];
+ const float radiusY = rectParams[1];
+ const float alpha = rectParams[2];
SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
SkPaint paint;
paint.setColor(SkColors::kBlack);
paint.setBlendMode(mHolePunchBlendMode);
+ paint.setAlphaf(alpha);
mWrappedCanvas->drawRRect(roundRect, paint);
}
}
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.h b/libs/hwui/pipeline/skia/TransformCanvas.h
index 685b71d017e9..15f0c1abc55a 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.h
+++ b/libs/hwui/pipeline/skia/TransformCanvas.h
@@ -19,6 +19,13 @@
#include "SkPaintFilterCanvas.h"
#include <effects/StretchEffect.h>
+class SkData;
+class SkDrawable;
+class SkMatrix;
+class SkPaint;
+enum class SkBlendMode;
+struct SkRect;
+
class TransformCanvas : public SkPaintFilterCanvas {
public:
TransformCanvas(SkCanvas* target, SkBlendMode blendmode) :
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index 3c7617d35c7c..e168a7b9459a 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -33,6 +33,8 @@
#include "thread/ThreadBase.h"
#include "utils/TimeUtils.h"
+#include <SkBlendMode.h>
+
namespace android {
namespace uirenderer {
namespace skiapipeline {
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index ded2b06fb3cf..23611efccd73 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -16,6 +16,15 @@
#include "CacheManager.h"
+#include <GrContextOptions.h>
+#include <SkExecutor.h>
+#include <SkGraphics.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+#include <set>
+
+#include "CanvasContext.h"
#include "DeviceInfo.h"
#include "Layer.h"
#include "Properties.h"
@@ -25,40 +34,44 @@
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
-#include <utils/Trace.h>
-
-#include <GrContextOptions.h>
-#include <SkExecutor.h>
-#include <SkGraphics.h>
-#include <SkMathPriv.h>
-#include <math.h>
-#include <set>
namespace android {
namespace uirenderer {
namespace renderthread {
-// This multiplier was selected based on historical review of cache sizes relative
-// to the screen resolution. This is meant to be a conservative default based on
-// that analysis. The 4.0f is used because the default pixel format is assumed to
-// be ARGB_8888.
-#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
-#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
-
-CacheManager::CacheManager()
- : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight())
- , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER)
- , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE)
- // This sets the maximum size for a single texture atlas in the GPU font cache. If
- // necessary, the cache can allocate additional textures that are counted against the
- // total cache limits provided to Skia.
- , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea))
- // This sets the maximum size of the CPU font cache to be at least the same size as the
- // total number of GPU font caches (i.e. 4 separate GPU atlases).
- , mMaxCpuFontCacheBytes(
- std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()))
- , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
+ mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
+ mMemoryPolicy.initialMaxSurfaceAreaScale);
+ setupCacheLimits();
+}
+
+static inline int countLeadingZeros(uint32_t mask) {
+ // __builtin_clz(0) is undefined, so we have to detect that case.
+ return mask ? __builtin_clz(mask) : 32;
+}
+
+// Return the smallest power-of-2 >= n.
+static inline uint32_t nextPowerOfTwo(uint32_t n) {
+ return n ? (1 << (32 - countLeadingZeros(n - 1))) : 1;
+}
+
+void CacheManager::setupCacheLimits() {
+ mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
+ mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
+ // This sets the maximum size for a single texture atlas in the GPU font cache. If
+ // necessary, the cache can allocate additional textures that are counted against the
+ // total cache limits provided to Skia.
+ mMaxGpuFontAtlasBytes = nextPowerOfTwo(mMaxSurfaceArea);
+ // This sets the maximum size of the CPU font cache to be at least the same size as the
+ // total number of GPU font caches (i.e. 4 separate GPU atlases).
+ mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
+ mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
+
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
+ if (mGrContext) {
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ }
}
void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +82,7 @@ void CacheManager::reset(sk_sp<GrDirectContext> context) {
if (context) {
mGrContext = std::move(context);
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
}
}
@@ -93,10 +107,9 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void
auto& cache = skiapipeline::ShaderCache::get();
cache.initShaderDiskCache(identity, size);
contextOptions->fPersistentCache = &cache;
- contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
-void CacheManager::trimMemory(TrimMemoryMode mode) {
+void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
@@ -104,21 +117,28 @@ void CacheManager::trimMemory(TrimMemoryMode mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
+ mode = TrimLevel::COMPLETE;
+ }
+
switch (mode) {
- case TrimMemoryMode::Complete:
+ case TrimLevel::COMPLETE:
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
break;
- case TrimMemoryMode::UiHidden:
+ case TrimLevel::UI_HIDDEN:
// Here we purge all the unlocked scratch resources and then toggle the resources cache
// limits between the background and max amounts. This causes the unlocked resources
// that have persistent data to be purged in LRU order.
- mGrContext->purgeUnlockedResources(true);
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
+ default:
+ break;
}
}
@@ -147,11 +167,29 @@ void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
}
void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+ log.appendFormat(R"(Memory policy:
+ Max surface area: %zu
+ Max resource usage: %.2fMB (x%.0f)
+ Background retention: %.0f%% (altUiHidden = %s)
+)",
+ mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
+ mMemoryPolicy.surfaceSizeMultiplier,
+ mMemoryPolicy.backgroundRetentionPercent * 100.0f,
+ mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
+ if (Properties::isSystemOrPersistent) {
+ log.appendFormat(" IsSystemOrPersistent\n");
+ }
+ log.appendFormat(" GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
+ size_t stoppedContexts = 0;
+ for (auto context : mCanvasContexts) {
+ if (context->isStopped()) stoppedContexts++;
+ }
+ log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+
if (!mGrContext) {
- log.appendFormat("No valid cache instance.\n");
+ log.appendFormat("No GPU context.\n");
return;
}
-
std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
{"skia/sk_resource_cache/bitmap_", "Bitmaps"},
{"skia/sk_resource_cache/rrect-blur_", "Masks"},
@@ -199,6 +237,8 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
}
void CacheManager::onFrameCompleted() {
+ cancelDestroyContext();
+ mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
@@ -210,11 +250,82 @@ void CacheManager::onFrameCompleted() {
}
}
-void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) {
- if (mGrContext) {
- mGrContext->performDeferredCleanup(
- std::chrono::milliseconds(cleanupOlderThanMillis),
- /* scratchResourcesOnly */true);
+void CacheManager::onThreadIdle() {
+ if (!mGrContext || mFrameCompletions.size() == 0) return;
+
+ const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ // Rate limiting
+ if ((now - mLastDeferredCleanup) < 25_ms) {
+ mLastDeferredCleanup = now;
+ const nsecs_t frameCompleteNanos = mFrameCompletions[0];
+ const nsecs_t frameDiffNanos = now - frameCompleteNanos;
+ const nsecs_t cleanupMillis =
+ ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
+ mMemoryPolicy.purgeScratchOnly);
+ }
+}
+
+void CacheManager::scheduleDestroyContext() {
+ if (mMemoryPolicy.contextTimeout > 0) {
+ mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
+ [this, genId = mGenerationId] {
+ if (mGenerationId != genId) return;
+ // GenID should have already stopped this, but just in
+ // case
+ if (!areAllContextsStopped()) return;
+ mRenderThread.destroyRenderingContext();
+ });
+ }
+}
+
+void CacheManager::cancelDestroyContext() {
+ if (mIsDestructionPending) {
+ mIsDestructionPending = false;
+ mGenerationId++;
+ }
+}
+
+bool CacheManager::areAllContextsStopped() {
+ for (auto context : mCanvasContexts) {
+ if (!context->isStopped()) return false;
+ }
+ return true;
+}
+
+void CacheManager::checkUiHidden() {
+ if (!mGrContext) return;
+
+ if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
+ trimMemory(TrimLevel::UI_HIDDEN);
+ }
+}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {
+ mCanvasContexts.push_back(context);
+ cancelDestroyContext();
+}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {
+ std::erase(mCanvasContexts, context);
+ checkUiHidden();
+ if (mCanvasContexts.empty()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::onContextStopped(CanvasContext* context) {
+ checkUiHidden();
+ if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {
+ int frameArea = width * height;
+ if (frameArea > mMaxSurfaceArea) {
+ mMaxSurfaceArea = frameArea;
+ setupCacheLimits();
}
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index af82672c6f23..d21ac9badc43 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -22,7 +22,11 @@
#endif
#include <SkSurface.h>
#include <utils/String8.h>
+
#include <vector>
+
+#include "MemoryPolicy.h"
+#include "utils/RingBuffer.h"
#include "utils/TimeUtils.h"
namespace android {
@@ -35,17 +39,15 @@ class RenderState;
namespace renderthread {
-class IRenderPipeline;
class RenderThread;
+class CanvasContext;
class CacheManager {
public:
- enum class TrimMemoryMode { Complete, UiHidden };
-
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
#endif
- void trimMemory(TrimMemoryMode mode);
+ void trimMemory(TrimLevel mode);
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
@@ -53,30 +55,50 @@ public:
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
void onFrameCompleted();
+ void notifyNextFrameSize(int width, int height);
+
+ void onThreadIdle();
- void performDeferredCleanup(nsecs_t cleanupOlderThanMillis);
+ void registerCanvasContext(CanvasContext* context);
+ void unregisterCanvasContext(CanvasContext* context);
+ void onContextStopped(CanvasContext* context);
private:
friend class RenderThread;
- explicit CacheManager();
+ explicit CacheManager(RenderThread& thread);
+ void setupCacheLimits();
+ bool areAllContextsStopped();
+ void checkUiHidden();
+ void scheduleDestroyContext();
+ void cancelDestroyContext();
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void reset(sk_sp<GrDirectContext> grContext);
#endif
void destroy();
- const size_t mMaxSurfaceArea;
+ RenderThread& mRenderThread;
+ const MemoryPolicy& mMemoryPolicy;
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
sk_sp<GrDirectContext> mGrContext;
#endif
- const size_t mMaxResourceBytes;
- const size_t mBackgroundResourceBytes;
+ size_t mMaxSurfaceArea = 0;
+
+ size_t mMaxResourceBytes = 0;
+ size_t mBackgroundResourceBytes = 0;
+
+ size_t mMaxGpuFontAtlasBytes = 0;
+ size_t mMaxCpuFontCacheBytes = 0;
+ size_t mBackgroundCpuFontCacheBytes = 0;
+
+ std::vector<CanvasContext*> mCanvasContexts;
+ RingBuffer<uint64_t, 100> mFrameCompletions;
- const size_t mMaxGpuFontAtlasBytes;
- const size_t mMaxCpuFontCacheBytes;
- const size_t mBackgroundCpuFontCacheBytes;
+ nsecs_t mLastDeferredCleanup = 0;
+ bool mIsDestructionPending = false;
+ uint32_t mGenerationId = 0;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 602554ab82f3..dd781bb85470 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -42,9 +42,6 @@
#include "utils/GLUtils.h"
#include "utils/TimeUtils.h"
-#define TRIM_MEMORY_COMPLETE 80
-#define TRIM_MEMORY_UI_HIDDEN 20
-
#define LOG_FRAMETIME_MMA 0
#if LOG_FRAMETIME_MMA
@@ -74,16 +71,19 @@ CanvasContext* ScopedActiveContext::sActiveContext = nullptr;
} /* namespace */
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
- RenderNode* rootRenderNode, IContextFactory* contextFactory) {
+ RenderNode* rootRenderNode, IContextFactory* contextFactory,
+ int32_t uiThreadId, int32_t renderThreadId) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
- std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
+ std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread),
+ uiThreadId, renderThreadId);
case RenderPipelineType::SkiaVulkan:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
- std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
+ std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread),
+ uiThreadId, renderThreadId);
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
@@ -113,7 +113,8 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory,
- std::unique_ptr<IRenderPipeline> renderPipeline)
+ std::unique_ptr<IRenderPipeline> renderPipeline, pid_t uiThreadId,
+ pid_t renderThreadId)
: mRenderThread(thread)
, mGenerationID(0)
, mOpaque(!translucent)
@@ -121,7 +122,9 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mJankTracker(&thread.globalProfileData())
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
- , mRenderPipeline(std::move(renderPipeline)) {
+ , mRenderPipeline(std::move(renderPipeline))
+ , mHintSessionWrapper(uiThreadId, renderThreadId) {
+ mRenderThread.cacheManager().registerCanvasContext(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -133,6 +136,7 @@ CanvasContext::~CanvasContext() {
node->clearRoot();
}
mRenderNodes.clear();
+ mRenderThread.cacheManager().unregisterCanvasContext(this);
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -149,11 +153,13 @@ void CanvasContext::removeRenderNode(RenderNode* node) {
void CanvasContext::destroy() {
stopDrawing();
+ setHardwareBuffer(nullptr);
setSurface(nullptr);
setSurfaceControl(nullptr);
freePrefetchedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
+ mRenderThread.cacheManager().onContextStopped(this);
}
static void setBufferCount(ANativeWindow* window) {
@@ -171,6 +177,19 @@ static void setBufferCount(ANativeWindow* window) {
native_window_set_buffer_count(window, bufferCount);
}
+void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+ mRenderPipeline->setHardwareBuffer(mHardwareBuffer);
+}
+
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
@@ -229,6 +248,8 @@ void CanvasContext::setupPipelineSurface() {
// Order is important when new and old surfaces are the same, because old surface has
// its frame stats disabled automatically.
native_window_enable_frame_timestamps(mNativeSurface->getNativeWindow(), true);
+ native_window_set_scaling_mode(mNativeSurface->getNativeWindow(),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE);
} else {
mRenderThread.removeFrameCallback(this);
mGenerationID++;
@@ -251,7 +272,8 @@ void CanvasContext::setStopped(bool stopped) {
mGenerationID++;
mRenderThread.removeFrameCallback(this);
mRenderPipeline->onStop();
- } else if (mIsDirty && hasSurface()) {
+ mRenderThread.cacheManager().onContextStopped(this);
+ } else if (mIsDirty && hasOutputTarget()) {
mRenderThread.postFrameCallback(this);
}
}
@@ -277,9 +299,43 @@ void CanvasContext::setOpaque(bool opaque) {
mOpaque = opaque;
}
-void CanvasContext::setColorMode(ColorMode mode) {
- mRenderPipeline->setSurfaceColorProperties(mode);
- setupPipelineSurface();
+float CanvasContext::setColorMode(ColorMode mode) {
+ if (mode != mColorMode) {
+ const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
+ if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
+ mode = ColorMode::WideColorGamut;
+ }
+ mColorMode = mode;
+ mRenderPipeline->setSurfaceColorProperties(mode);
+ setupPipelineSurface();
+ }
+ switch (mColorMode) {
+ case ColorMode::Hdr:
+ return Properties::maxHdrHeadroomOn8bit;
+ case ColorMode::Hdr10:
+ return 10.f;
+ default:
+ return 1.f;
+ }
+}
+
+float CanvasContext::targetSdrHdrRatio() const {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
+ return mTargetSdrHdrRatio;
+ } else {
+ return 1.f;
+ }
+}
+
+void CanvasContext::setTargetSdrHdrRatio(float ratio) {
+ if (mTargetSdrHdrRatio == ratio) return;
+
+ mTargetSdrHdrRatio = ratio;
+ mRenderPipeline->setTargetSdrHdrRatio(ratio);
+ // We don't actually but we need to behave as if we do. Specifically we need to ensure
+ // all buffers in the swapchain are fully re-rendered as any partial updates to them will
+ // result in mixed target white points which looks really bad & flickery
+ mHaveNewSurface = true;
}
bool CanvasContext::makeCurrent() {
@@ -384,7 +440,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
mIsDirty = true;
- if (CC_UNLIKELY(!hasSurface())) {
+ if (CC_UNLIKELY(!hasOutputTarget())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
return;
@@ -461,7 +517,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
}
void CanvasContext::stopDrawing() {
- cleanupResources();
mRenderThread.removeFrameCallback(this);
mAnimationContext->pauseAnimators();
mGenerationID++;
@@ -470,18 +525,25 @@ void CanvasContext::stopDrawing() {
void CanvasContext::notifyFramePending() {
ATRACE_CALL();
mRenderThread.pushBackFrameCallback(this);
+ sendLoadResetHint();
}
-nsecs_t CanvasContext::draw() {
+void CanvasContext::draw() {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
- return 0;
+ return;
}
}
SkRect dirty;
mDamageAccumulator.finish(&dirty);
+ // reset syncDelayDuration each time we draw
+ nsecs_t syncDelayDuration = mSyncDelayDuration;
+ nsecs_t idleDuration = mIdleDuration;
+ mSyncDelayDuration = 0;
+ mIdleDuration = 0;
+
if (!Properties::isDrawingEnabled() ||
(dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -498,7 +560,7 @@ nsecs_t CanvasContext::draw() {
std::invoke(func, false /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
- return 0;
+ return;
}
ScopedActiveContext activeContext(this);
@@ -523,7 +585,7 @@ nsecs_t CanvasContext::draw() {
std::scoped_lock lock(mFrameMetricsReporterMutex);
drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
&mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()));
+ mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
}
uint64_t frameCompleteNr = getFrameNumber();
@@ -543,6 +605,8 @@ nsecs_t CanvasContext::draw() {
}
bool requireSwap = false;
+ bool didDraw = false;
+
int error = OK;
bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
mCurrentFrameInfo, &requireSwap);
@@ -553,7 +617,7 @@ nsecs_t CanvasContext::draw() {
mIsDirty = false;
if (requireSwap) {
- bool didDraw = true;
+ didDraw = true;
// Handle any swapchain errors
error = mNativeSurface->getAndClearError();
if (error == TIMED_OUT) {
@@ -648,23 +712,25 @@ nsecs_t CanvasContext::draw() {
}
}
- cleanupResources();
- mRenderThread.cacheManager().onFrameCompleted();
- return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
-}
+ int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync);
+ int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
+ int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
-void CanvasContext::cleanupResources() {
- auto& tracker = mJankTracker.frames();
- auto size = tracker.size();
- auto capacity = tracker.capacity();
- if (size == capacity) {
- nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC);
- nsecs_t frameCompleteNanos =
- tracker[0].get(FrameInfoIndex::FrameCompleted);
- nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos;
- nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s));
- mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis);
+ mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+
+ if (didDraw) {
+ int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+ int64_t actualDuration = frameDuration -
+ (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+ dequeueBufferDuration - idleDuration;
+ mHintSessionWrapper.reportActualWorkDuration(actualDuration);
}
+
+ mLastDequeueBufferDuration = dequeueBufferDuration;
+
+ mRenderThread.cacheManager().onFrameCompleted();
+ return;
}
void CanvasContext::reportMetricsWithPresentTime() {
@@ -777,6 +843,8 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
if (!mRenderPipeline->isSurfaceReady()) return;
+ mIdleDuration =
+ systemTime(SYSTEM_TIME_MONOTONIC) - mRenderThread.timeLord().computeFrameTimeNanos();
prepareAndDraw(nullptr);
}
@@ -790,6 +858,7 @@ SkISize CanvasContext::getNextFrameSize() const {
SkISize size;
size.fWidth = ANativeWindow_getWidth(anw);
size.fHeight = ANativeWindow_getHeight(anw);
+ mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight);
return size;
}
@@ -868,18 +937,6 @@ void CanvasContext::destroyHardwareResources() {
}
}
-void CanvasContext::trimMemory(RenderThread& thread, int level) {
- ATRACE_CALL();
- if (!thread.getGrContext()) return;
- ATRACE_CALL();
- if (level >= TRIM_MEMORY_COMPLETE) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
- thread.destroyRenderingContext();
- } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
- }
-}
-
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
@@ -996,6 +1053,22 @@ void CanvasContext::prepareSurfaceControlForWebview() {
}
}
+void CanvasContext::sendLoadResetHint() {
+ mHintSessionWrapper.sendLoadResetHint();
+}
+
+void CanvasContext::sendLoadIncreaseHint() {
+ mHintSessionWrapper.sendLoadIncreaseHint();
+}
+
+void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
+ mSyncDelayDuration = duration;
+}
+
+void CanvasContext::startHintSession() {
+ mHintSessionWrapper.init();
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 951ee216ce35..b26c018e86fb 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -16,10 +16,26 @@
#pragma once
+#include <SkBitmap.h>
+#include <SkRect.h>
+#include <SkSize.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <future>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ColorMode.h"
#include "DamageAccumulator.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
#include "FrameMetricsReporter.h"
+#include "HintSessionWrapper.h"
#include "IContextFactory.h"
#include "IRenderPipeline.h"
#include "JankTracker.h"
@@ -30,21 +46,6 @@
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
#include "utils/RingBuffer.h"
-#include "ColorMode.h"
-
-#include <SkBitmap.h>
-#include <SkRect.h>
-#include <SkSize.h>
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
-#include <utils/Mutex.h>
-
-#include <functional>
-#include <future>
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
namespace android {
namespace uirenderer {
@@ -66,7 +67,8 @@ class Frame;
class CanvasContext : public IFrameCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
- IContextFactory* contextFactory);
+ IContextFactory* contextFactory, pid_t uiThreadId,
+ pid_t renderThreadId);
virtual ~CanvasContext();
/**
@@ -123,21 +125,25 @@ public:
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
+ void setHardwareBuffer(AHardwareBuffer* buffer);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);
bool pauseSurface();
void setStopped(bool stopped);
- bool hasSurface() const { return mNativeSurface.get(); }
+ bool isStopped() { return mStopped || !hasOutputTarget(); }
+ bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; }
void allocateBuffers();
void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightGeometry(const Vector3& lightCenter, float lightRadius);
void setOpaque(bool opaque);
- void setColorMode(ColorMode mode);
+ float setColorMode(ColorMode mode);
+ float targetSdrHdrRatio() const;
+ void setTargetSdrHdrRatio(float ratio);
bool makeCurrent();
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target);
// Returns the DequeueBufferDuration.
- nsecs_t draw();
+ void draw();
void destroy();
// IFrameCallback, Choreographer-driven frame callback entry point
@@ -148,7 +154,6 @@ public:
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
- static void trimMemory(RenderThread& thread, int level);
DeferredLayerUpdater* createTextureLayer();
@@ -204,6 +209,10 @@ public:
mASurfaceTransactionCallback = callback;
}
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mBufferParams = params;
+ }
+
bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control);
void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback) {
@@ -214,9 +223,18 @@ public:
static CanvasContext* getActiveContext();
+ void sendLoadResetHint();
+
+ void sendLoadIncreaseHint();
+
+ void setSyncDelayDuration(nsecs_t duration);
+
+ void startHintSession();
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
- IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
+ IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
+ pid_t uiThreadId, pid_t renderThreadId);
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -251,6 +269,9 @@ private:
int32_t mLastFrameHeight = 0;
RenderThread& mRenderThread;
+
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+ HardwareBufferRenderParams mBufferParams;
std::unique_ptr<ReliableSurface> mNativeSurface;
// The SurfaceControl reference is passed from ViewRootImpl, can be set to
// NULL to remove the reference
@@ -331,7 +352,13 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
- void cleanupResources();
+ HintSessionWrapper mHintSessionWrapper;
+ nsecs_t mLastDequeueBufferDuration = 0;
+ nsecs_t mSyncDelayDuration = 0;
+ nsecs_t mIdleDuration = 0;
+
+ ColorMode mColorMode = ColorMode::Default;
+ float mTargetSdrHdrRatio = 1.f;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 3c75499391f6..fab2f46e91c3 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -16,9 +16,9 @@
#include "DrawFrameTask.h"
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <utils/Log.h>
+
#include <algorithm>
#include "../DeferredLayerUpdater.h"
@@ -26,64 +26,13 @@
#include "../Properties.h"
#include "../RenderNode.h"
#include "CanvasContext.h"
+#include "HardwareBufferRenderParams.h"
#include "RenderThread.h"
-#include "thread/CommonPool.h"
namespace android {
namespace uirenderer {
namespace renderthread {
-namespace {
-
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
- size_t, int64_t);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
- if (gAPerformanceHintBindingInitialized) return;
-
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
-
- gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
- LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
- "Failed to find required symbol APerformanceHint_getManager!");
-
- gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
- LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_createSession!");
-
- gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
- handle_, "APerformanceHint_updateTargetWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_updateTargetWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
- gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
- handle_, "APerformanceHint_reportActualWorkDuration");
- LOG_ALWAYS_FATAL_IF(
- gAPH_reportActualWorkDurationFn == nullptr,
- "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
-
- gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
- LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_closeSession!");
-
- gAPerformanceHintBindingInitialized = true;
-}
-
-} // namespace
-
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
@@ -92,13 +41,11 @@ DrawFrameTask::DrawFrameTask()
DrawFrameTask::~DrawFrameTask() {}
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
- int32_t uiThreadId, int32_t renderThreadId) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+ RenderNode* targetNode) {
mRenderThread = thread;
mContext = context;
mTargetNode = targetNode;
- mUiThreadId = uiThreadId;
- mRenderThreadId = renderThreadId;
}
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -142,8 +89,13 @@ void DrawFrameTask::postAndWait() {
void DrawFrameTask::run() {
const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);
- nsecs_t syncDelayDuration = systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued;
+ mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
+ mContext->setTargetSdrHdrRatio(mRenderSdrHdrRatio);
+
+ auto hardwareBufferParams = mHardwareBufferParams;
+ mContext->setHardwareBufferRenderParams(hardwareBufferParams);
+ IRenderPipeline* pipeline = mContext->getRenderPipeline();
bool canUnblockUiThread;
bool canDrawThisFrame;
{
@@ -166,9 +118,6 @@ void DrawFrameTask::run() {
std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
mFrameCallback = nullptr;
mFrameCompleteCallback = nullptr;
- int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
- int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
- int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)];
// From this point on anything in "this" is *UNSAFE TO ACCESS*
if (canUnblockUiThread) {
@@ -179,16 +128,15 @@ void DrawFrameTask::run() {
if (CC_UNLIKELY(frameCallback)) {
context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
frameNr = context->getFrameNumber()]() {
- auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
+ auto frameCommitCallback = frameCallback(syncResult, frameNr);
if (frameCommitCallback) {
context->addFrameCommitListener(std::move(frameCommitCallback));
}
});
}
- nsecs_t dequeueBufferDuration = 0;
if (CC_LIKELY(canDrawThisFrame)) {
- dequeueBufferDuration = context->draw();
+ context->draw();
} else {
// Do a flush in case syncFrameState performed any texture uploads. Since we skipped
// the draw() call, those uploads (or deletes) will end up sitting in the queue.
@@ -208,26 +156,10 @@ void DrawFrameTask::run() {
unblockUiThread();
}
- if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
- constexpr int64_t kSanityCheckLowerBound = 100000; // 0.1ms
- constexpr int64_t kSanityCheckUpperBound = 10000000000; // 10s
- int64_t targetWorkDuration = frameDeadline - intendedVsync;
- targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
- if (targetWorkDuration > kSanityCheckLowerBound &&
- targetWorkDuration < kSanityCheckUpperBound &&
- targetWorkDuration != mLastTargetWorkDuration) {
- mLastTargetWorkDuration = targetWorkDuration;
- mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
+ if (pipeline->hasHardwareBuffer()) {
+ auto fence = pipeline->flush();
+ hardwareBufferParams.invokeRenderCallback(std::move(fence), 0);
}
- int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
- int64_t actualDuration = frameDuration -
- (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
- dequeueBufferDuration;
- if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
- mHintSessionWrapper->reportActualWorkDuration(actualDuration);
- }
-
- mLastDequeueBufferDuration = dequeueBufferDuration;
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
@@ -253,8 +185,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
- if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
- if (!mContext->hasSurface()) {
+ bool hasTarget = mContext->hasOutputTarget();
+ if (CC_UNLIKELY(!hasTarget || !canDraw)) {
+ if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
} else {
// If we have a surface but can't draw we must be stopped
@@ -280,49 +213,6 @@ void DrawFrameTask::unblockUiThread() {
mSignal.signal();
}
-void DrawFrameTask::createHintSession(pid_t uiThreadId, pid_t renderThreadId) {
- if (mHintSessionWrapper) return;
- mHintSessionWrapper.emplace(uiThreadId, renderThreadId);
-}
-
-DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
- if (!Properties::useHintManager) return;
- if (uiThreadId < 0 || renderThreadId < 0) return;
-
- ensureAPerformanceHintBindingInitialized();
-
- APerformanceHintManager* manager = gAPH_getManagerFn();
- if (!manager) return;
-
- std::vector<int32_t> tids = CommonPool::getThreadIds();
- tids.push_back(uiThreadId);
- tids.push_back(renderThreadId);
-
- // DrawFrameTask code will always set a target duration before reporting actual durations.
- // So this is just a placeholder value that's never used.
- int64_t dummyTargetDurationNanos = 16666667;
- mHintSession =
- gAPH_createSessionFn(manager, tids.data(), tids.size(), dummyTargetDurationNanos);
-}
-
-DrawFrameTask::HintSessionWrapper::~HintSessionWrapper() {
- if (mHintSession) {
- gAPH_closeSessionFn(mHintSession);
- }
-}
-
-void DrawFrameTask::HintSessionWrapper::updateTargetWorkDuration(long targetDurationNanos) {
- if (mHintSession) {
- gAPH_updateTargetWorkDurationFn(mHintSession, targetDurationNanos);
- }
-}
-
-void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
- if (mHintSession) {
- gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
- }
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index b135a215d180..4130d4abe09e 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,7 +16,6 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
@@ -28,8 +27,16 @@
#include "../Rect.h"
#include "../TreeInfo.h"
#include "RenderTask.h"
+#include "SkColorSpace.h"
+#include "SwapBehavior.h"
+#include "utils/TimeUtils.h"
+#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
+#include <android/hardware_buffer.h>
+#endif
+#include "HardwareBufferRenderParams.h"
namespace android {
+
namespace uirenderer {
class DeferredLayerUpdater;
@@ -61,8 +68,7 @@ public:
DrawFrameTask();
virtual ~DrawFrameTask();
- void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
- int32_t uiThreadId, int32_t renderThreadId);
+ void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void setContentDrawBounds(int left, int top, int right, int bottom) {
mContentDrawBounds.set(left, top, right, bottom);
}
@@ -90,21 +96,13 @@ public:
void forceDrawNextFrame() { mForceDrawFrame = true; }
- void createHintSession(pid_t uiThreadId, pid_t renderThreadId);
-
-private:
- class HintSessionWrapper {
- public:
- HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId);
- ~HintSessionWrapper();
-
- void updateTargetWorkDuration(long targetDurationNanos);
- void reportActualWorkDuration(long actualDurationNanos);
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mHardwareBufferParams = params;
+ }
- private:
- APerformanceHintSession* mHintSession = nullptr;
- };
+ void setRenderSdrHdrRatio(float ratio) { mRenderSdrHdrRatio = ratio; }
+private:
void postAndWait();
bool syncFrameState(TreeInfo& info);
void unblockUiThread();
@@ -115,9 +113,8 @@ private:
RenderThread* mRenderThread;
CanvasContext* mContext;
RenderNode* mTargetNode = nullptr;
- int32_t mUiThreadId = -1;
- int32_t mRenderThreadId = -1;
Rect mContentDrawBounds;
+ float mRenderSdrHdrRatio = 1.f;
/*********************************************
* Single frame data
@@ -129,14 +126,11 @@ private:
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
+ HardwareBufferRenderParams mHardwareBufferParams;
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;
};
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 02257db9df6a..4fb114b71bf5 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -450,6 +450,12 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
+ // Extended Range HDR requires being able to manipulate the dataspace in ways
+ // we cannot easily do while going through EGLSurface. Given this requires
+ // composer3 support, just treat HDR as equivalent to wide color gamut if
+ // the GLES path is still being hit
+ case ColorMode::Hdr:
+ case ColorMode::Hdr10:
case ColorMode::WideColorGamut: {
skcms_Matrix3x3 colorGamut;
LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
@@ -466,14 +472,6 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
}
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;
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index fc6b28d2e1ad..b8f8c9267ad8 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -18,6 +18,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
+#include <SkColorSpace.h>
#include <SkImageInfo.h>
#include <SkRect.h>
#include <cutils/compiler.h>
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
new file mode 100644
index 000000000000..91fe3f6cf273
--- /dev/null
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 HARDWAREBUFFERRENDERER_H_
+#define HARDWAREBUFFERRENDERER_H_
+
+#include <android-base/unique_fd.h>
+#include <android/hardware_buffer.h>
+
+#include "SkColorSpace.h"
+#include "SkMatrix.h"
+#include "SkSurface.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+using namespace android::uirenderer::renderthread;
+
+using RenderCallback = std::function<void(android::base::unique_fd&&, int)>;
+
+class RenderProxy;
+
+class HardwareBufferRenderParams {
+public:
+ HardwareBufferRenderParams() = default;
+ HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
+ RenderCallback&& callback)
+ : mTransform(transform)
+ , mColorSpace(colorSpace)
+ , mRenderCallback(std::move(callback)) {}
+ const SkMatrix& getTransform() const { return mTransform; }
+ sk_sp<SkColorSpace> getColorSpace() const { return mColorSpace; }
+
+ void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) {
+ if (mRenderCallback) {
+ std::invoke(mRenderCallback, std::move(fenceFd), status);
+ }
+ }
+
+private:
+ SkMatrix mTransform = SkMatrix::I();
+ sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB();
+ RenderCallback mRenderCallback = nullptr;
+};
+
+} // namespace renderthread
+} // namespace uirenderer
+} // namespace android
+#endif // HARDWAREBUFFERRENDERER_H_
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
new file mode 100644
index 000000000000..8c9f65fac10c
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#include "HintSessionWrapper.h"
+
+#include <dlfcn.h>
+#include <private/performance_hint_private.h>
+#include <utils/Log.h>
+
+#include <vector>
+
+#include "../Properties.h"
+#include "thread/CommonPool.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+namespace {
+
+typedef APerformanceHintManager* (*APH_getManager)();
+typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
+ size_t, int64_t);
+typedef void (*APH_closeSession)(APerformanceHintSession* session);
+typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
+
+bool gAPerformanceHintBindingInitialized = false;
+APH_getManager gAPH_getManagerFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
+APH_closeSession gAPH_closeSessionFn = nullptr;
+APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
+APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_sendHint gAPH_sendHintFn = nullptr;
+
+void ensureAPerformanceHintBindingInitialized() {
+ if (gAPerformanceHintBindingInitialized) return;
+
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
+
+ gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
+ LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
+ "Failed to find required symbol APerformanceHint_getManager!");
+
+ gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+ LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+ "Failed to find required symbol APerformanceHint_createSession!");
+
+ gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+ LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
+ "Failed to find required symbol APerformanceHint_closeSession!");
+
+ gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
+ handle_, "APerformanceHint_updateTargetWorkDuration");
+ LOG_ALWAYS_FATAL_IF(
+ gAPH_updateTargetWorkDurationFn == nullptr,
+ "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
+
+ gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
+ handle_, "APerformanceHint_reportActualWorkDuration");
+ LOG_ALWAYS_FATAL_IF(
+ gAPH_reportActualWorkDurationFn == nullptr,
+ "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+
+ gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
+ LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
+ "Failed to find required symbol APerformanceHint_sendHint!");
+
+ gAPerformanceHintBindingInitialized = true;
+}
+
+} // namespace
+
+HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
+ : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+
+HintSessionWrapper::~HintSessionWrapper() {
+ if (mHintSession) {
+ gAPH_closeSessionFn(mHintSession);
+ }
+}
+
+bool HintSessionWrapper::init() {
+ // If it already exists, broke last time we tried this, shouldn't be running, or
+ // has bad argument values, don't even bother
+ if (mHintSession != nullptr || !mSessionValid || !Properties::useHintManager ||
+ !Properties::isDrawingEnabled() || mUiThreadId < 0 || mRenderThreadId < 0) {
+ return false;
+ }
+
+ // Assume that if we return before the end, it broke
+ mSessionValid = false;
+
+ ensureAPerformanceHintBindingInitialized();
+
+ APerformanceHintManager* manager = gAPH_getManagerFn();
+ if (!manager) return false;
+
+ std::vector<pid_t> tids = CommonPool::getThreadIds();
+ tids.push_back(mUiThreadId);
+ tids.push_back(mRenderThreadId);
+
+ // Use a placeholder target value to initialize,
+ // this will always be replaced elsewhere before it gets used
+ int64_t defaultTargetDurationNanos = 16666667;
+ mHintSession =
+ gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+
+ mSessionValid = !!mHintSession;
+ return mSessionValid;
+}
+
+void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {
+ if (mHintSession == nullptr) return;
+ targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100;
+ if (targetWorkDurationNanos != mLastTargetWorkDuration &&
+ targetWorkDurationNanos > kSanityCheckLowerBound &&
+ targetWorkDurationNanos < kSanityCheckUpperBound) {
+ mLastTargetWorkDuration = targetWorkDurationNanos;
+ gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+ }
+ mLastFrameNotification = systemTime();
+}
+
+void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
+ if (mHintSession == nullptr) return;
+ if (actualDurationNanos > kSanityCheckLowerBound &&
+ actualDurationNanos < kSanityCheckUpperBound) {
+ gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+ }
+}
+
+void HintSessionWrapper::sendLoadResetHint() {
+ if (mHintSession == nullptr) return;
+ nsecs_t now = systemTime();
+ if (now - mLastFrameNotification > kResetHintTimeout) {
+ gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+ }
+ mLastFrameNotification = now;
+}
+
+void HintSessionWrapper::sendLoadIncreaseHint() {
+ if (mHintSession == nullptr) return;
+ gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
new file mode 100644
index 000000000000..f2f1298c1eec
--- /dev/null
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/performance_hint.h>
+
+#include "utils/TimeUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace renderthread {
+
+class HintSessionWrapper {
+public:
+ HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
+ ~HintSessionWrapper();
+
+ void updateTargetWorkDuration(long targetDurationNanos);
+ void reportActualWorkDuration(long actualDurationNanos);
+ void sendLoadResetHint();
+ void sendLoadIncreaseHint();
+ bool init();
+
+private:
+ APerformanceHintSession* mHintSession = nullptr;
+
+ nsecs_t mLastFrameNotification = 0;
+ nsecs_t mLastTargetWorkDuration = 0;
+
+ pid_t mUiThreadId;
+ pid_t mRenderThreadId;
+
+ bool mSessionValid = true;
+
+ static constexpr nsecs_t kResetHintTimeout = 100_ms;
+ static constexpr int64_t kSanityCheckLowerBound = 100_us;
+ static constexpr int64_t kSanityCheckUpperBound = 10_s;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index ef58bc553c23..c68fcdfc76f2 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -16,16 +16,19 @@
#pragma once
+#include <SkColorSpace.h>
+#include <SkRect.h>
+#include <android-base/unique_fd.h>
+#include <utils/RefBase.h>
+
+#include "ColorMode.h"
#include "DamageAccumulator.h"
#include "FrameInfoVisualizer.h"
+#include "HardwareBufferRenderParams.h"
#include "LayerUpdateQueue.h"
#include "Lighting.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
-#include "ColorMode.h"
-
-#include <SkRect.h>
-#include <utils/RefBase.h>
class GrDirectContext;
@@ -63,10 +66,14 @@ public:
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
- FrameInfoVisualizer* profiler) = 0;
+ FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) = 0;
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
+ [[nodiscard]] virtual android::base::unique_fd flush() = 0;
+ virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
+ virtual bool hasHardwareBuffer() = 0;
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
@@ -88,6 +95,9 @@ public:
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
+ virtual bool supportsExtendedRangeHdr() const { return false; }
+ virtual void setTargetSdrHdrRatio(float ratio) = 0;
+
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index d9b650c1ff37..31b4b203c670 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -29,6 +29,10 @@
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
+
#include <pthread.h>
namespace android {
@@ -41,15 +45,14 @@ RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
pid_t uiThreadId = pthread_gettid_np(pthread_self());
pid_t renderThreadId = getRenderThreadTid();
mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
- CanvasContext* context =
- CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+ CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
+ contextFactory, uiThreadId, renderThreadId);
if (context != nullptr) {
- mRenderThread.queue().post(
- [=] { mDrawFrameTask.createHintSession(uiThreadId, renderThreadId); });
+ mRenderThread.queue().post([=] { context->startHintSession(); });
}
return context;
});
- mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode, uiThreadId, renderThreadId);
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
RenderProxy::~RenderProxy() {
@@ -58,7 +61,7 @@ RenderProxy::~RenderProxy() {
void RenderProxy::destroyContext() {
if (mContext) {
- mDrawFrameTask.setContext(nullptr, nullptr, nullptr, -1, -1);
+ mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
mRenderThread.queue().runSync([this]() { delete mContext; });
@@ -86,6 +89,18 @@ void RenderProxy::setName(const char* name) {
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
}
+void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ }
+ mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable {
+ mContext->setHardwareBuffer(hardwareBuffer);
+ if (hardwareBuffer) {
+ AHardwareBuffer_release(hardwareBuffer);
+ }
+ });
+}
+
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
if (window) { ANativeWindow_acquire(window); }
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
@@ -132,8 +147,20 @@ void RenderProxy::setOpaque(bool opaque) {
mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); });
}
-void RenderProxy::setColorMode(ColorMode mode) {
- mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+float RenderProxy::setColorMode(ColorMode mode) {
+ // We only need to figure out what the renderer supports for HDR, otherwise this can stay
+ // an async call since we already know the return value
+ if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
+ return mRenderThread.queue().runSync(
+ [=]() -> float { return mContext->setColorMode(mode); });
+ } else {
+ mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+ return 1.f;
+ }
+}
+
+void RenderProxy::setRenderSdrHdrRatio(float ratio) {
+ mDrawFrameTask.setRenderSdrHdrRatio(ratio);
}
int64_t* RenderProxy::frameInfo() {
@@ -199,7 +226,8 @@ void RenderProxy::trimMemory(int level) {
// Avoid creating a RenderThread to do a trimMemory.
if (RenderThread::hasInstance()) {
RenderThread& thread = RenderThread::getInstance();
- thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); });
+ const auto trimLevel = static_cast<TrimLevel>(level);
+ thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); });
}
}
@@ -208,7 +236,7 @@ void RenderProxy::purgeCaches() {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread]() {
if (thread.getGrContext()) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ thread.cacheManager().trimMemory(TrimLevel::COMPLETE);
}
});
}
@@ -238,6 +266,14 @@ void RenderProxy::notifyFramePending() {
mRenderThread.queue().post([this]() { mContext->notifyFramePending(); });
}
+void RenderProxy::notifyCallbackPending() {
+ mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); });
+}
+
+void RenderProxy::notifyExpensiveFrame() {
+ mRenderThread.queue().post([this]() { mContext->sendLoadIncreaseHint(); });
+}
+
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
mRenderThread.queue().runSync([&]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
@@ -320,6 +356,10 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom)
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
+void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mDrawFrameTask.setHardwareBufferRenderParams(params);
+}
+
void RenderProxy::setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) {
mRenderThread.queue().post(
@@ -367,12 +407,13 @@ void RenderProxy::setForceDark(bool enable) {
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
-int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
- SkBitmap* bitmap) {
+void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
auto& thread = RenderThread::getInstance();
- return static_cast<int>(thread.queue().runSync([&]() -> auto {
- return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
- }));
+ ANativeWindow_acquire(window);
+ thread.queue().post([&thread, window, request = std::move(request)] {
+ thread.readback().copySurfaceInto(window, request);
+ ANativeWindow_release(window);
+ });
}
void RenderProxy::prepareToDraw(Bitmap& bitmap) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index ee9efd46e307..82072a6e2499 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -17,19 +17,25 @@
#ifndef RENDERPROXY_H_
#define RENDERPROXY_H_
-#include <SkBitmap.h>
+#include <SkRefCnt.h>
+#include <android/hardware_buffer.h>
#include <android/native_window.h>
-#include <cutils/compiler.h>
#include <android/surface_control.h>
+#include <cutils/compiler.h>
#include <utils/Functor.h>
#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
#include "ColorMode.h"
+#include "CopyRequest.h"
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
+class SkBitmap;
+class SkPicture;
+class SkImage;
+
namespace android {
class GraphicBuffer;
class Surface;
@@ -71,7 +77,7 @@ public:
void setSwapBehavior(SwapBehavior swapBehavior);
bool loadSystemProperties();
void setName(const char* name);
-
+ void setHardwareBuffer(AHardwareBuffer* buffer);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);
void allocateBuffers();
@@ -79,8 +85,10 @@ public:
void setStopped(bool stopped);
void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightGeometry(const Vector3& lightCenter, float lightRadius);
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params);
void setOpaque(bool opaque);
- void setColorMode(ColorMode mode);
+ float setColorMode(ColorMode mode);
+ void setRenderSdrHdrRatio(float ratio);
int64_t* frameInfo();
void forceDrawNextFrame();
int syncAndDrawFrame();
@@ -104,6 +112,8 @@ public:
static int maxTextureSize();
void stopDrawing();
void notifyFramePending();
+ void notifyCallbackPending();
+ void notifyExpensiveFrame();
void dumpProfileInfo(int fd, int dumpFlags);
// Not exported, only used for testing
@@ -133,8 +143,7 @@ public:
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
void setForceDark(bool enable);
- static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
- int bottom, SkBitmap* bitmap);
+ static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
static void prepareToDraw(Bitmap& bitmap);
static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 01b956cb3dd5..0afd949cf5c9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -135,7 +135,9 @@ void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t
!mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
- nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+
+ nsecs_t timeUntilDeadline = frameDeadline - frameTimeNanos;
+ nsecs_t runAt = (frameTimeNanos + (timeUntilDeadline * 0.25f));
queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
}
}
@@ -251,13 +253,12 @@ void RenderThread::initThreadLocals() {
mEglManager = new EglManager();
mRenderState = new RenderState(*this);
mVkManager = VulkanManager::getInstance();
- mCacheManager = new CacheManager();
+ mCacheManager = new CacheManager(*this);
}
void RenderThread::setupFrameInterval() {
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
mTimeLord.setFrameInterval(frameIntervalNanos);
- mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}
void RenderThread::requireGlContext() {
@@ -266,7 +267,7 @@ void RenderThread::requireGlContext() {
}
mEglManager->initialize();
- sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
+ sk_sp<const GrGLInterface> glInterface = GrGLMakeNativeInterface();
LOG_ALWAYS_FATAL_IF(!glInterface.get());
GrContextOptions options;
@@ -453,6 +454,8 @@ bool RenderThread::threadLoop() {
// next vsync (oops), so none of the callbacks are run.
requestVsync();
}
+
+ mCacheManager->onThreadIdle();
}
return false;
@@ -502,6 +505,11 @@ void RenderThread::preload() {
HardwareBitmapUploader::initialize();
}
+void RenderThread::trimMemory(TrimLevel level) {
+ ATRACE_CALL();
+ cacheManager().trimMemory(level);
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index c1f6790b25b2..c77cd4134d1e 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,11 +17,11 @@
#ifndef RENDERTHREAD_H_
#define RENDERTHREAD_H_
-#include <surface_control_private.h>
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
#include <private/android/choreographer.h>
+#include <surface_control_private.h>
#include <thread/ThreadBase.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
@@ -31,6 +31,7 @@
#include <set>
#include "CacheManager.h"
+#include "MemoryPolicy.h"
#include "ProfileDataContainer.h"
#include "RenderTask.h"
#include "TimeLord.h"
@@ -172,6 +173,8 @@ public:
return mASurfaceControlFunctions;
}
+ void trimMemory(TrimLevel level);
+
/**
* isCurrent provides a way to query, if the caller is running on
* the render thread.
@@ -232,7 +235,6 @@ private:
bool mFrameCallbackTaskPending;
TimeLord mTimeLord;
- nsecs_t mDispatchFrameDelay = 4_ms;
RenderState* mRenderState;
EglManager* mEglManager;
WebViewFunctorManager& mFunctorManager;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b8c2bdf112f8..c5196eeccea3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -51,7 +51,8 @@ typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore
#include "VulkanSurface.h"
#include "private/hwui/DrawVkInfo.h"
-class GrVkExtensions;
+#include <SkColorSpace.h>
+#include <SkRefCnt.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 7dd3561cb220..21b6c44e997e 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -199,7 +199,14 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType);
outWindowInfo->colorspace = colorSpace;
- outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
+ outWindowInfo->colorMode = colorMode;
+
+ if (colorMode == ColorMode::Hdr || colorMode == ColorMode::Hdr10) {
+ outWindowInfo->dataspace =
+ static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
+ } else {
+ outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
+ }
LOG_ALWAYS_FATAL_IF(
outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN && colorType != kAlpha_8_SkColorType,
"Unsupported colorspace");
@@ -496,6 +503,33 @@ int VulkanSurface::getCurrentBuffersAge() {
return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0;
}
+void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) {
+ mWindowInfo.colorspace = std::move(colorSpace);
+ for (int i = 0; i < kNumBufferSlots; i++) {
+ mNativeBuffers[i].skSurface.reset();
+ }
+
+ if (mWindowInfo.colorMode == ColorMode::Hdr || mWindowInfo.colorMode == ColorMode::Hdr10) {
+ mWindowInfo.dataspace =
+ static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
+ } else {
+ mWindowInfo.dataspace = ColorSpaceToADataSpace(
+ mWindowInfo.colorspace.get(), BufferFormatToColorType(mWindowInfo.bufferFormat));
+ }
+ LOG_ALWAYS_FATAL_IF(mWindowInfo.dataspace == HAL_DATASPACE_UNKNOWN &&
+ mWindowInfo.bufferFormat != AHARDWAREBUFFER_FORMAT_R8_UNORM,
+ "Unsupported colorspace");
+
+ if (mNativeWindow) {
+ int err = ANativeWindow_setBuffersDataSpace(mNativeWindow.get(), mWindowInfo.dataspace);
+ if (err != 0) {
+ ALOGE("VulkanSurface::setColorSpace() native_window_set_buffers_data_space(%d) "
+ "failed: %s (%d)",
+ mWindowInfo.dataspace, strerror(-err), err);
+ }
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index beb71b727f51..e2ddc6b07768 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -20,6 +20,7 @@
#include <system/window.h>
#include <vulkan/vulkan.h>
+#include <SkColorSpace.h>
#include <SkRefCnt.h>
#include <SkSize.h>
@@ -45,6 +46,8 @@ public:
}
const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; }
+ void setColorSpace(sk_sp<SkColorSpace> colorSpace);
+
private:
/*
* All structs/methods in this private section are specifically for use by the VulkanManager
@@ -93,6 +96,7 @@ private:
uint32_t bufferFormat;
android_dataspace dataspace;
sk_sp<SkColorSpace> colorspace;
+ ColorMode colorMode;
int transform;
size_t bufferCount;
uint64_t windowUsageFlags;
diff --git a/libs/hwui/tests/common/BitmapAllocationTestUtils.h b/libs/hwui/tests/common/BitmapAllocationTestUtils.h
index 312b60bbc067..bbdd98e0c0d1 100644
--- a/libs/hwui/tests/common/BitmapAllocationTestUtils.h
+++ b/libs/hwui/tests/common/BitmapAllocationTestUtils.h
@@ -56,10 +56,11 @@ public:
template <class BaseScene>
static bool registerBitmapAllocationScene(std::string name, std::string description) {
- TestScene::registerScene({name + "GlTex", description + " (GlTex version).",
+ TestScene::registerScene({name + "Texture", description + " (Texture version).",
createBitmapAllocationScene<BaseScene, &allocateHeapBitmap>});
- TestScene::registerScene({name + "EglImage", description + " (EglImage version).",
+ TestScene::registerScene({name + "HardwareBuffer",
+ description + " (HardwareBuffer version).",
createBitmapAllocationScene<BaseScene, &allocateHardwareBitmap>});
return true;
}
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index d3c41191eef1..dc36a2e01815 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -19,6 +19,8 @@
#include <SkCanvasVirtualEnforcer.h>
#include <SkNoDrawCanvas.h>
+enum class SkBlendMode;
+
namespace android {
namespace uirenderer {
namespace test {
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 898c64bd4159..fd596d998dfd 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -28,10 +28,11 @@ const ui::StaticDisplayInfo& getDisplayInfo() {
#if HWUI_NULL_GPU
info.density = 2.f;
#else
- const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
- LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
+ const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
- const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info);
+ const status_t status =
+ SurfaceComposerClient::getStaticDisplayInfo(ids.front().value, &info);
LOG_ALWAYS_FATAL_IF(status, "%s: Failed to get display info", __FUNCTION__);
#endif
return info;
@@ -48,7 +49,10 @@ const ui::DisplayMode& getActiveDisplayMode() {
config.xDpi = config.yDpi = 320.f;
config.refreshRate = 60.f;
#else
- const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
+ const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
+
+ const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
const status_t status = SurfaceComposerClient::getActiveDisplayMode(token, &config);
diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp
index 43df4a0b1576..e70d44c9c60a 100644
--- a/libs/hwui/tests/common/TestListViewSceneBase.cpp
+++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp
@@ -19,6 +19,8 @@
#include "TestContext.h"
#include "TestUtils.h"
+#include <SkBlendMode.h>
+
#include <utils/Color.h>
namespace android {
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 491af4336f97..a4890ede8faa 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -26,7 +26,13 @@
#include <renderthread/VulkanManager.h>
#include <utils/Unicode.h>
+#include "SkCanvas.h"
#include "SkColorData.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
+#include "SkPixmap.h"
+#include "SkRect.h"
+#include "SkSurface.h"
#include "SkUnPreMultiply.h"
namespace android {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index fcaa745e9fc6..9d5c13e5cd75 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -28,10 +28,20 @@
#include <renderstate/RenderState.h>
#include <renderthread/RenderThread.h>
+#include <SkBitmap.h>
+#include <SkColor.h>
+#include <SkImageInfo.h>
+#include <SkRefCnt.h>
+
#include <gtest/gtest.h>
#include <memory>
#include <unordered_map>
+class SkCanvas;
+class SkMatrix;
+class SkPath;
+struct SkRect;
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
index 5af7d43d7f66..19e87f851827 100644
--- a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp
@@ -19,6 +19,7 @@
#include "utils/Color.h"
#include <SkBitmap.h>
+#include <SkBlendMode.h>
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index 03aeb55f129b..a07cdf720b50 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -14,7 +14,17 @@
* limitations under the License.
*/
-#include <SkImagePriv.h>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkImage.h>
+#include <SkImageInfo.h>
+#include <SkPaint.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkSamplingOptions.h>
+#include <SkShader.h>
+#include <SkTileMode.h>
#include "hwui/Paint.h"
#include "TestSceneBase.h"
#include "tests/common/BitmapAllocationTestUtils.h"
diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
index 2a016ac1b5bc..3a1ea8c29963 100644
--- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class ClippingAnimation;
static TestScene::Registrar _RectGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index 4271d2f04b88..484289a8ef1d 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -20,6 +20,8 @@
#include <hwui/Paint.h>
#include <minikin/Layout.h>
+#include <SkBlendMode.h>
+
#include <cstdio>
class GlyphStressAnimation;
diff --git a/libs/hwui/tests/common/scenes/HwBitmap565.cpp b/libs/hwui/tests/common/scenes/HwBitmap565.cpp
index cbdb756b8fa7..de0ef6d595f8 100644
--- a/libs/hwui/tests/common/scenes/HwBitmap565.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmap565.cpp
@@ -18,6 +18,12 @@
#include "tests/common/BitmapAllocationTestUtils.h"
#include "utils/Color.h"
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+
class HwBitmap565;
static TestScene::Registrar _HwBitmap565(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index 564354f04674..dfdd0d8727b9 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -17,6 +17,8 @@
#include "TestSceneBase.h"
#include "utils/Color.h"
+#include <SkBlendMode.h>
+#include <SkColorSpace.h>
#include <SkGradientShader.h>
#include <SkImagePriv.h>
#include <ui/PixelFormat.h>
diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
index cac2fb3d8d5c..2955fb25ec2c 100644
--- a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class HwLayerAnimation;
static TestScene::Registrar _HwLayer(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
index 77a59dfe6ba5..8c9a6147f47d 100644
--- a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class HwLayerSizeAnimation;
static TestScene::Registrar _HwLayerSize(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/JankyScene.cpp b/libs/hwui/tests/common/scenes/JankyScene.cpp
index f5e6b317529a..250b986e7e73 100644
--- a/libs/hwui/tests/common/scenes/JankyScene.cpp
+++ b/libs/hwui/tests/common/scenes/JankyScene.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
#include <unistd.h>
class JankyScene;
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 5eaf1853233a..f669dbc9323e 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,6 +17,7 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
#include "hwui/Paint.h"
+#include <SkBlendMode.h>
#include <SkGradientShader.h>
class ListOfFadedTextAnimation;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index d031923a112b..4a5d9468cd88 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -17,7 +17,16 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
#include "hwui/Paint.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
#include <SkFont.h>
+#include <SkFontTypes.h>
+#include <SkPaint.h>
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkScalar.h>
#include <cstdio>
class ListViewAnimation;
@@ -48,7 +57,7 @@ class ListViewAnimation : public TestListViewSceneBase {
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font;
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index edadf78db051..13a438199ae5 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -19,19 +19,57 @@
#include "utils/Color.h"
#include "hwui/Paint.h"
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkFont.h>
+
class MagnifierAnimation;
+using Rect = android::uirenderer::Rect;
+
static TestScene::Registrar _Magnifier(TestScene::Info{
"magnifier", "A sample magnifier using Readback",
TestScene::simpleCreateScene<MagnifierAnimation>});
+class BlockingCopyRequest : public CopyRequest {
+ sk_sp<Bitmap> mDestination;
+ std::mutex mLock;
+ std::condition_variable mCondVar;
+ CopyResult mResult;
+
+public:
+ BlockingCopyRequest(::Rect rect, sk_sp<Bitmap> bitmap)
+ : CopyRequest(rect), mDestination(bitmap) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ SkBitmap bitmap;
+ mDestination->getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ std::unique_lock _lock{mLock};
+ mResult = result;
+ mCondVar.notify_all();
+ }
+
+ CopyResult waitForResult() {
+ std::unique_lock _lock{mLock};
+ mCondVar.wait(_lock);
+ return mResult;
+ }
+};
+
class MagnifierAnimation : public TestScene {
public:
sp<RenderNode> card;
sp<RenderNode> zoomImageView;
+ sk_sp<Bitmap> magnifier;
+ std::shared_ptr<BlockingCopyRequest> copyRequest;
void createContent(int width, int height, Canvas& canvas) override {
magnifier = TestUtils::createBitmap(200, 100);
+ setupCopyRequest();
SkBitmap temp;
magnifier->getSkBitmap(&temp);
temp.eraseColor(Color::White);
@@ -61,19 +99,20 @@ public:
canvas.enableZ(false);
}
+ void setupCopyRequest() {
+ constexpr int x = 90;
+ constexpr int y = 325;
+ copyRequest = std::make_shared<BlockingCopyRequest>(
+ ::Rect(x, y, x + magnifier->width(), y + magnifier->height()), magnifier);
+ }
+
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
card->mutateStagingProperties().setTranslationX(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
if (renderTarget) {
- SkBitmap temp;
- magnifier->getSkBitmap(&temp);
- constexpr int x = 90;
- constexpr int y = 325;
- RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
- y + magnifier->height(), &temp);
+ RenderProxy::copySurfaceInto(renderTarget.get(), copyRequest);
+ copyRequest->waitForResult();
}
}
-
- sk_sp<Bitmap> magnifier;
};
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
index 402c1ece2146..1a2af8382ad7 100644
--- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -17,6 +17,8 @@
#include "TestSceneBase.h"
#include "utils/Color.h"
+#include <SkBlendMode.h>
+
class OvalAnimation;
static TestScene::Registrar _Oval(TestScene::Info{"oval", "Draws 1 oval.",
diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
index fb1b000a995e..25cf4d61bf9d 100644
--- a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class PartialDamageAnimation;
static TestScene::Registrar _PartialDamage(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
index 1e343c1dd283..969514c50d14 100644
--- a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
@@ -16,6 +16,8 @@
#include <vector>
+#include <SkBlendMode.h>
+
#include "TestSceneBase.h"
class PathClippingAnimation : public TestScene {
diff --git a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
index 716d3979bdcb..3caaf8236d8a 100644
--- a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
+++ b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
@@ -16,6 +16,12 @@
#include "TestSceneBase.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+
class ReadbackFromHardware;
static TestScene::Registrar _SaveLayer(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
index 1c2507867f6e..27948f8b4b43 100644
--- a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
@@ -17,6 +17,11 @@
#include "TestSceneBase.h"
#include "utils/Color.h"
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkColor.h>
+#include <SkRefCnt.h>
+
class RecentsAnimation;
static TestScene::Registrar _Recents(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
index f37bcbc3ee1b..99e785887b16 100644
--- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class RectGridAnimation;
static TestScene::Registrar _RectGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index e9f353d887f2..2c27969487d3 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
#include <vector>
class RoundRectClippingAnimation : public TestScene {
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 252f539ffca9..ee30c131efbd 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -16,6 +16,7 @@
#include <hwui/Paint.h>
#include <minikin/Layout.h>
+#include <SkBlendMode.h>
#include <string>
#include "TestSceneBase.h"
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
index 31a8ae1d38cd..d5060c758f93 100644
--- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class SaveLayerAnimation;
static TestScene::Registrar _SaveLayer(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
index c13e80e8c204..827ddab118d9 100644
--- a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class ShadowGrid2Animation;
static TestScene::Registrar _ShadowGrid2(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
index 772b98e32220..a4fb10c5081e 100644
--- a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class ShadowGridAnimation;
static TestScene::Registrar _ShadowGrid(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
index 0019da5fd80b..58c03727bc29 100644
--- a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
@@ -16,6 +16,8 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
+
class ShadowShaderAnimation;
static TestScene::Registrar _ShadowShader(TestScene::Info{
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
index 70a1557dcf6a..c0c3dfd9a8c4 100644
--- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -17,6 +17,8 @@
#include "TestSceneBase.h"
#include "utils/Color.h"
+#include <SkBlendMode.h>
+
#include <cstdio>
class ShapeAnimation;
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index a0bc5aa245d5..40f2ed081626 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -16,7 +16,9 @@
#include "TestSceneBase.h"
-#include <SkColorMatrixFilter.h>
+#include <SkBlendMode.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
#include <SkGradientShader.h>
class SimpleColorMatrixAnimation;
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 57a260c8d234..a9e7a34b5b3f 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -16,6 +16,7 @@
#include "TestSceneBase.h"
+#include <SkBlendMode.h>
#include <SkGradientShader.h>
class SimpleGradientAnimation;
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index e677549b7894..bb95490c1d39 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -14,7 +14,16 @@
* limitations under the License.
*/
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
#include <SkFont.h>
+#include <SkFontTypes.h>
+#include <SkPaint.h>
+#include <SkPoint.h>
+#include <SkRefCnt.h>
+#include <SkRRect.h>
#include <cstdio>
#include "TestSceneBase.h"
#include "hwui/Paint.h"
@@ -130,7 +139,7 @@ private:
roundRectPaint.setColor(Color::White);
if (addHolePunch) {
// Punch a hole but then cover it up, we don't want to actually see it
- canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)));
+ canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)), 1.f);
}
canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
@@ -227,4 +236,4 @@ class StretchyUniformLayerListViewHolePunch : public StretchyListViewAnimation {
StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
bool haveHolePunch() override { return true; }
bool forceLayer() override { return true; }
-}; \ No newline at end of file
+};
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index d30903679bce..78146b8cabf2 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -17,6 +17,8 @@
#include "TestSceneBase.h"
#include "hwui/Paint.h"
+#include <SkBlendMode.h>
+
class TextAnimation;
static TestScene::Registrar _Text(TestScene::Info{"text", "Draws a bunch of text.",
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index c6219c485b85..aff8ca1e26c7 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -14,7 +14,12 @@
* limitations under the License.
*/
+#include "SkBitmap.h"
#include "SkBlendMode.h"
+#include "SkColorFilter.h"
+#include "SkFont.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
#include "TestSceneBase.h"
#include "tests/common/BitmapAllocationTestUtils.h"
#include "hwui/Paint.h"
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
index 9cd10759a834..a55b72534924 100644
--- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
@@ -22,6 +22,8 @@
#include "pipeline/skia/SkiaDisplayList.h"
#include "tests/common/TestUtils.h"
+#include <SkBlendMode.h>
+
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::skiapipeline;
diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp
index 6aed251481bf..72946c4abdf0 100644
--- a/libs/hwui/tests/microbench/RenderNodeBench.cpp
+++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp
@@ -19,6 +19,8 @@
#include "hwui/Canvas.h"
#include "RenderNode.h"
+#include <SkBlendMode.h>
+
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 2ec78a429481..138b3efd10ed 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -29,7 +29,7 @@ AHardwareBuffer* allocHardwareBuffer() {
.height = 16,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+ .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
constexpr int kSucceeded = 0;
int status = AHardwareBuffer_allocate(&desc, &buffer);
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index fc84abb1d605..2b90bda87ecd 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -21,6 +21,7 @@
#include "tests/common/TestUtils.h"
#include <SkImagePriv.h>
+#include "include/gpu/GpuTypes.h" // from Skia
using namespace android;
using namespace android::uirenderer;
@@ -45,7 +46,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
SkImageInfo info = SkImageInfo::MakeA8(width, height);
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kYes, info);
+ sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
+ info);
surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
grContext->flushAndSubmit();
@@ -59,7 +61,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
// attempt to trim all memory while we still hold strong refs
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
// free the surfaces
@@ -76,11 +78,11 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
// UI hidden and make sure only some got purged (unique should remain)
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+ renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN);
ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
// complete and make sure all get purged
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
}
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 1771c3590e10..9e376e32f8ea 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -36,9 +36,9 @@ RENDERTHREAD_TEST(CanvasContext, create) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
- ASSERT_FALSE(canvasContext->hasSurface());
+ ASSERT_FALSE(canvasContext->hasOutputTarget());
canvasContext->destroy();
}
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 2cf3456694b0..1f6edf36af25 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -23,9 +23,18 @@
#include <tests/common/CallCountingCanvas.h>
-#include "SkPictureRecorder.h"
+#include "SkBlendMode.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
#include "SkColor.h"
+#include "SkImageInfo.h"
#include "SkLatticeIter.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPictureRecorder.h"
+#include "SkRRect.h"
+#include "SkRect.h"
+#include "SkRegion.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include <SkNoDrawCanvas.h>
diff --git a/libs/hwui/tests/unit/EglManagerTests.cpp b/libs/hwui/tests/unit/EglManagerTests.cpp
index 7f2e1589ae6c..ec9ab90fa46b 100644
--- a/libs/hwui/tests/unit/EglManagerTests.cpp
+++ b/libs/hwui/tests/unit/EglManagerTests.cpp
@@ -20,6 +20,8 @@
#include "renderthread/RenderEffectCapabilityQuery.h"
#include "tests/common/TestContext.h"
+#include <SkColorSpace.h>
+
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 2a74afc5bb7a..96a0c6114682 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -19,6 +19,8 @@
#include <SkCanvas.h>
#include <gtest/gtest.h>
+class SkRRect;
+
namespace {
class TestCanvasBase : public SkCanvas {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index ec949b80ea55..596bd37e4cf5 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -17,6 +17,7 @@
#include <VectorDrawable.h>
#include <gtest/gtest.h>
+#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
#include <string.h>
@@ -334,7 +335,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
"A");
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -398,7 +399,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
"A");
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -518,7 +519,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
// prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -618,7 +619,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
// prepareTree is required to find, which receivers have backward projected nodes
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -634,7 +635,7 @@ namespace {
static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode) {
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, renderNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 61bd646b0a76..80796f4a4111 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -274,7 +274,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -310,7 +310,7 @@ RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
});
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
canvasContext->setSurface(nullptr);
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index dc1b2e668dd0..c1ddbd36bcfd 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -16,9 +16,14 @@
#include "tests/common/TestUtils.h"
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkColor.h>
#include <SkColorMatrixFilter.h>
#include <SkColorSpace.h>
-#include <SkImagePriv.h>
+#include <SkImageInfo.h>
+#include <SkPaint.h>
+#include <SkPath.h>
#include <SkPathOps.h>
#include <SkShader.h>
#include <gtest/gtest.h>
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index dae3c9435712..87c52161d68e 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,9 +17,19 @@
#include "tests/common/TestUtils.h"
#include <hwui/Paint.h>
+#include <SkAlphaType.h>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
#include <SkCanvasStateUtils.h>
+#include <SkColor.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <SkImageInfo.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
+#include <SkRefCnt.h>
+#include <SkSurface.h>
#include <gtest/gtest.h>
using namespace android;
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 3d5aca4bf05a..f825d7c5d9cc 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -142,7 +142,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
DamageAccumulator damageAccumulator;
info.damageAccumulator = &damageAccumulator;
@@ -201,7 +201,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
- CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
// Set up a Surface so that we can position the VectorDrawable offscreen.
test::TestContext testContext;
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 7419f8fd89f1..4d0595e03da6 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -17,6 +17,7 @@
#include <VectorDrawable.h>
#include <gtest/gtest.h>
+#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
#include <string.h>
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index 15ecf5831f3a..ced667eb76e5 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -17,6 +17,7 @@
#include <VectorDrawable.h>
#include <gtest/gtest.h>
+#include <SkCanvas.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
#include <string.h>
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index ab23448ab93f..499afa039d1f 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -21,8 +21,11 @@
#include <sys/stat.h>
#include <utils/Log.h>
+#include "SkData.h"
#include "SkFontMgr.h"
+#include "SkRefCnt.h"
#include "SkStream.h"
+#include "SkTypeface.h"
#include "hwui/MinikinSkia.h"
#include "hwui/Typeface.h"
@@ -61,7 +64,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
std::vector<minikin::FontVariation>());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
- return std::make_shared<minikin::FontFamily>(std::move(fonts));
+ return minikin::FontFamily::create(std::move(fonts));
}
std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) {
@@ -70,7 +73,8 @@ std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const ch
TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
// Keep the original to restore it later.
@@ -348,24 +352,24 @@ TEST(TypefaceTest, createAbsolute) {
TEST(TypefaceTest, createFromFamilies_Single) {
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
- std::unique_ptr<Typeface> regular(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
+ std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
- std::unique_ptr<Typeface> bold(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
+ std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
- std::unique_ptr<Typeface> italic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
+ std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -373,8 +377,8 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -382,8 +386,8 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
- std::unique_ptr<Typeface> over1000(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
+ std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
EXPECT_EQ(1000, over1000->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
@@ -391,30 +395,33 @@ TEST(TypefaceTest, createFromFamilies_Single) {
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
// In Java, new Typeface.Builder("Family-Regular.ttf").build();
- std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new Typeface.Builder("Family-Bold.ttf").build();
- std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
- makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> bold(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new Typeface.Builder("Family-Italic.ttf").build();
- std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
- makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> italic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
// In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -424,8 +431,9 @@ TEST(TypefaceTest, createFromFamilies_Family) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
@@ -433,10 +441,24 @@ TEST(TypefaceTest, createFromFamilies_Family) {
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
+TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
+ std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = {
+ buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
+ std::unique_ptr<Typeface> fallback(
+ Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, fallback.get()));
+ EXPECT_EQ(400, regular->fStyle.weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+}
+
} // namespace
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 6d4c57413f00..c1c21bd7dfbf 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -21,6 +21,12 @@
#include "utils/MathUtils.h"
#include "utils/VectorDrawableUtils.h"
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPath.h>
+#include <SkRefCnt.h>
+#include <SkShader.h>
+
#include <functional>
namespace android {
diff --git a/libs/hwui/utils/AutoMalloc.h b/libs/hwui/utils/AutoMalloc.h
new file mode 100644
index 000000000000..05f5e9f24133
--- /dev/null
+++ b/libs/hwui/utils/AutoMalloc.h
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+namespace android {
+namespace uirenderer {
+
+/** Manages an array of T elements, freeing the array in the destructor.
+ * Does NOT call any constructors/destructors on T (T must be POD).
+ */
+template <typename T,
+ typename = std::enable_if_t<std::is_trivially_default_constructible<T>::value &&
+ std::is_trivially_destructible<T>::value>>
+class AutoTMalloc {
+public:
+ /** Takes ownership of the ptr. The ptr must be a value which can be passed to std::free. */
+ explicit AutoTMalloc(T* ptr = nullptr) : fPtr(ptr) {}
+
+ /** Allocates space for 'count' Ts. */
+ explicit AutoTMalloc(size_t count) : fPtr(mallocIfCountThrowOnFail(count)) {}
+
+ AutoTMalloc(AutoTMalloc&&) = default;
+ AutoTMalloc& operator=(AutoTMalloc&&) = default;
+
+ /** Resize the memory area pointed to by the current ptr preserving contents. */
+ void realloc(size_t count) { fPtr.reset(reallocIfCountThrowOnFail(count)); }
+
+ /** Resize the memory area pointed to by the current ptr without preserving contents. */
+ T* reset(size_t count = 0) {
+ fPtr.reset(mallocIfCountThrowOnFail(count));
+ return this->get();
+ }
+
+ T* get() const { return fPtr.get(); }
+
+ operator T*() { return fPtr.get(); }
+
+ operator const T*() const { return fPtr.get(); }
+
+ T& operator[](int index) { return fPtr.get()[index]; }
+
+ const T& operator[](int index) const { return fPtr.get()[index]; }
+
+ /**
+ * Transfer ownership of the ptr to the caller, setting the internal
+ * pointer to NULL. Note that this differs from get(), which also returns
+ * the pointer, but it does not transfer ownership.
+ */
+ T* release() { return fPtr.release(); }
+
+private:
+ struct FreeDeleter {
+ void operator()(uint8_t* p) { std::free(p); }
+ };
+ std::unique_ptr<T, FreeDeleter> fPtr;
+
+ T* mallocIfCountThrowOnFail(size_t count) {
+ T* newPtr = nullptr;
+ if (count) {
+ newPtr = (T*)std::malloc(count * sizeof(T));
+ LOG_ALWAYS_FATAL_IF(!newPtr, "failed to malloc %zu bytes", count * sizeof(T));
+ }
+ return newPtr;
+ }
+ T* reallocIfCountThrowOnFail(size_t count) {
+ T* newPtr = nullptr;
+ if (count) {
+ newPtr = (T*)std::realloc(fPtr.release(), count * sizeof(T));
+ LOG_ALWAYS_FATAL_IF(!newPtr, "failed to realloc %zu bytes", count * sizeof(T));
+ }
+ return newPtr;
+ }
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 3afb419f9b8b..bffe137c2bd3 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -101,6 +101,26 @@ uint32_t ColorTypeToBufferFormat(SkColorType colorType) {
return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
}
}
+
+SkColorType BufferFormatToColorType(uint32_t format) {
+ switch (format) {
+ case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+ return kN32_SkColorType;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+ return kN32_SkColorType;
+ case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+ return kRGB_565_SkColorType;
+ case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+ return kRGBA_1010102_SkColorType;
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+ return kRGBA_F16_SkColorType;
+ case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+ return kAlpha_8_SkColorType;
+ default:
+ ALOGV("Unsupported format: %d, return unknown by default", format);
+ return kUnknown_SkColorType;
+ }
+}
#endif
namespace {
@@ -155,23 +175,12 @@ android_dataspace ColorSpaceToADataSpace(SkColorSpace* colorSpace, SkColorType c
skcms_TransferFunction fn;
if (!colorSpace->isNumericalTransferFn(&fn)) {
- // pq with the default white point
- auto rec2020PQ = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020);
- if (SkColorSpace::Equals(colorSpace, rec2020PQ.get())) {
- return HAL_DATASPACE_BT2020_PQ;
- }
- // standard PQ
- rec2020PQ = SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, SkNamedGamut::kRec2020);
- if (SkColorSpace::Equals(colorSpace, rec2020PQ.get())) {
+ auto res = skcms_TransferFunction_getType(&fn);
+ if (res == skcms_TFType_PQish) {
return HAL_DATASPACE_BT2020_PQ;
}
- // HLG
- const auto hlgFn = GetHLGScaleTransferFunction();
- if (hlgFn.has_value()) {
- auto rec2020HLG = SkColorSpace::MakeRGB(hlgFn.value(), SkNamedGamut::kRec2020);
- if (SkColorSpace::Equals(colorSpace, rec2020HLG.get())) {
- return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
- }
+ if (res == skcms_TFType_HLGish) {
+ return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
}
LOG_ALWAYS_FATAL("Only select non-numerical transfer functions are supported");
}
@@ -396,6 +405,27 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) {
return fn;
}
+static skcms_TransferFunction trfn_apply_gain(const skcms_TransferFunction trfn, float gain) {
+ float pow_gain_ginv = sk_float_pow(gain, 1 / trfn.g);
+ skcms_TransferFunction result;
+ result.g = trfn.g;
+ result.a = trfn.a * pow_gain_ginv;
+ result.b = trfn.b * pow_gain_ginv;
+ result.c = trfn.c * gain;
+ result.d = trfn.d;
+ result.e = trfn.e * gain;
+ result.f = trfn.f * gain;
+ return result;
+}
+
+skcms_TransferFunction GetExtendedTransferFunction(float sdrHdrRatio) {
+ if (sdrHdrRatio <= 1.f) {
+ return SkNamedTransferFn::kSRGB;
+ }
+ // Scale the transfer by the sdrHdrRatio
+ return trfn_apply_gain(SkNamedTransferFn::kSRGB, sdrHdrRatio);
+}
+
// Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB
// but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100.
std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction() {
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 00f910f45c38..0fd61c7b990b 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -100,6 +100,7 @@ SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc,
sk_sp<SkColorSpace> colorSpace);
uint32_t ColorTypeToBufferFormat(SkColorType colorType);
+SkColorType BufferFormatToColorType(uint32_t bufferFormat);
#endif
ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
@@ -129,6 +130,7 @@ struct Lab {
Lab sRGBToLab(SkColor color);
SkColor LabToSRGB(const Lab& lab, SkAlpha alpha);
skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level = 0.f);
+skcms_TransferFunction GetExtendedTransferFunction(float sdrHdrRatio);
std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction();
} /* namespace uirenderer */
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 94bcb1110e05..f44f9d0fe2d4 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -19,6 +19,7 @@
#include <GLES2/gl2.h>
#include <utils/Blur.h>
+#include <SkBlendMode.h>
#include <SkColorFilter.h>
#include <SkPaint.h>
#include <SkShader.h>
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 45da008c3e8e..c3ad7670d473 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -22,14 +22,9 @@
#include "MouseCursorController.h"
+#include <input/Input.h>
#include <log/log.h>
-#include <SkBitmap.h>
-#include <SkBlendMode.h>
-#include <SkCanvas.h>
-#include <SkColor.h>
-#include <SkPaint.h>
-
namespace {
// Time to spend fading out the pointer completely.
const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
@@ -43,6 +38,8 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context)
: mContext(context) {
std::scoped_lock lock(mLock);
+ mLocked.stylusHoverMode = false;
+
mLocked.animationFrameIndex = 0;
mLocked.lastFrameUpdatedTime = 0;
@@ -52,11 +49,10 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context)
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
mLocked.updatePointerIcon = false;
- mLocked.requestedPointerType = mContext.getPolicy()->getDefaultPointerIconId();
+ mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
+ mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
mLocked.resourcesLoaded = false;
-
- mLocked.buttonState = 0;
}
MouseCursorController::~MouseCursorController() {
@@ -65,24 +61,23 @@ MouseCursorController::~MouseCursorController() {
mLocked.pointerSprite.clear();
}
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
std::scoped_lock lock(mLock);
- return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+ return getBoundsLocked();
}
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
if (!mLocked.viewport.isValid()) {
- return false;
+ return {};
}
- *outMinX = mLocked.viewport.logicalLeft;
- *outMinY = mLocked.viewport.logicalTop;
- *outMaxX = mLocked.viewport.logicalRight - 1;
- *outMaxY = mLocked.viewport.logicalBottom - 1;
- return true;
+ return FloatRect{
+ static_cast<float>(mLocked.viewport.logicalLeft),
+ static_cast<float>(mLocked.viewport.logicalTop),
+ static_cast<float>(mLocked.viewport.logicalRight - 1),
+ static_cast<float>(mLocked.viewport.logicalBottom - 1),
+ };
}
void MouseCursorController::move(float deltaX, float deltaY) {
@@ -98,22 +93,6 @@ void MouseCursorController::move(float deltaX, float deltaY) {
setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
}
-void MouseCursorController::setButtonState(int32_t buttonState) {
-#if DEBUG_MOUSE_CURSOR_UPDATES
- ALOGD("Set button state 0x%08x", buttonState);
-#endif
- std::scoped_lock lock(mLock);
-
- if (mLocked.buttonState != buttonState) {
- mLocked.buttonState = buttonState;
- }
-}
-
-int32_t MouseCursorController::getButtonState() const {
- std::scoped_lock lock(mLock);
- return mLocked.buttonState;
-}
-
void MouseCursorController::setPosition(float x, float y) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
@@ -123,31 +102,19 @@ void MouseCursorController::setPosition(float x, float y) {
}
void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- if (x <= minX) {
- mLocked.pointerX = minX;
- } else if (x >= maxX) {
- mLocked.pointerX = maxX;
- } else {
- mLocked.pointerX = x;
- }
- if (y <= minY) {
- mLocked.pointerY = minY;
- } else if (y >= maxY) {
- mLocked.pointerY = maxY;
- } else {
- mLocked.pointerY = y;
- }
- updatePointerLocked();
- }
+ const auto bounds = getBoundsLocked();
+ if (!bounds) return;
+
+ mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+ mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+ updatePointerLocked();
}
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
std::scoped_lock lock(mLock);
- *outX = mLocked.pointerX;
- *outY = mLocked.pointerY;
+ return {mLocked.pointerX, mLocked.pointerY};
}
int32_t MouseCursorController::getDisplayId() const {
@@ -189,6 +156,15 @@ void MouseCursorController::unfade(PointerControllerInterface::Transition transi
}
}
+void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) {
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.stylusHoverMode != stylusHoverMode) {
+ mLocked.stylusHoverMode = stylusHoverMode;
+ mLocked.updatePointerIcon = true;
+ }
+}
+
void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
std::scoped_lock lock(mLock);
@@ -204,8 +180,7 @@ static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, i
width = viewport.deviceWidth;
height = viewport.deviceHeight;
- if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
- viewport.orientation == DISPLAY_ORIENTATION_270) {
+ if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) {
std::swap(width, height);
}
}
@@ -229,10 +204,9 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
// Reset cursor position to center if size or display changed.
if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
oldDisplayHeight != newDisplayHeight) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- mLocked.pointerX = (minX + maxX) * 0.5f;
- mLocked.pointerY = (minY + maxY) * 0.5f;
+ if (const auto bounds = getBoundsLocked(); bounds) {
+ mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+ mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
@@ -249,38 +223,42 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
// Undo the previous rotation.
switch (oldViewport.orientation) {
- case DISPLAY_ORIENTATION_90:
+ case ui::ROTATION_90:
temp = x;
x = oldViewport.deviceHeight - y;
y = temp;
break;
- case DISPLAY_ORIENTATION_180:
+ case ui::ROTATION_180:
x = oldViewport.deviceWidth - x;
y = oldViewport.deviceHeight - y;
break;
- case DISPLAY_ORIENTATION_270:
+ case ui::ROTATION_270:
temp = x;
x = y;
y = oldViewport.deviceWidth - temp;
break;
+ case ui::ROTATION_0:
+ break;
}
// Perform the new rotation.
switch (viewport.orientation) {
- case DISPLAY_ORIENTATION_90:
+ case ui::ROTATION_90:
temp = x;
x = y;
y = viewport.deviceHeight - temp;
break;
- case DISPLAY_ORIENTATION_180:
+ case ui::ROTATION_180:
x = viewport.deviceWidth - x;
y = viewport.deviceHeight - y;
break;
- case DISPLAY_ORIENTATION_270:
+ case ui::ROTATION_270:
temp = x;
x = viewport.deviceWidth - y;
y = temp;
break;
+ case ui::ROTATION_0:
+ break;
}
// Apply offsets to convert from the pixel center to the pixel top-left corner position
@@ -292,7 +270,7 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
updatePointerLocked();
}
-void MouseCursorController::updatePointerIcon(int32_t iconId) {
+void MouseCursorController::updatePointerIcon(PointerIconStyle iconId) {
std::scoped_lock lock(mLock);
if (mLocked.requestedPointerType != iconId) {
@@ -305,7 +283,7 @@ void MouseCursorController::updatePointerIcon(int32_t iconId) {
void MouseCursorController::setCustomPointerIcon(const SpriteIcon& icon) {
std::scoped_lock lock(mLock);
- const int32_t iconId = mContext.getPolicy()->getCustomPointerIconId();
+ const PointerIconStyle iconId = mContext.getPolicy()->getCustomPointerIconId();
mLocked.additionalMouseResources[iconId] = icon;
mLocked.requestedPointerType = iconId;
mLocked.updatePointerIcon = true;
@@ -340,8 +318,8 @@ bool MouseCursorController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(
}
bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
- std::map<int32_t, PointerAnimation>::const_iterator iter =
- mLocked.animationResources.find(mLocked.requestedPointerType);
+ std::map<PointerIconStyle, PointerAnimation>::const_iterator iter =
+ mLocked.animationResources.find(mLocked.resolvedPointerType);
if (iter == mLocked.animationResources.end()) {
return false;
}
@@ -383,14 +361,23 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
}
if (mLocked.updatePointerIcon) {
- if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
+ mLocked.resolvedPointerType = mLocked.requestedPointerType;
+ const PointerIconStyle defaultPointerIconId =
+ mContext.getPolicy()->getDefaultPointerIconId();
+ if (mLocked.resolvedPointerType == PointerIconStyle::TYPE_NOT_SPECIFIED) {
+ mLocked.resolvedPointerType = mLocked.stylusHoverMode
+ ? mContext.getPolicy()->getDefaultStylusIconId()
+ : defaultPointerIconId;
+ }
+
+ if (mLocked.resolvedPointerType == defaultPointerIconId) {
mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
} else {
- std::map<int32_t, SpriteIcon>::const_iterator iter =
- mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
+ std::map<PointerIconStyle, SpriteIcon>::const_iterator iter =
+ mLocked.additionalMouseResources.find(mLocked.resolvedPointerType);
if (iter != mLocked.additionalMouseResources.end()) {
- std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
- mLocked.animationResources.find(mLocked.requestedPointerType);
+ std::map<PointerIconStyle, PointerAnimation>::const_iterator anim_iter =
+ mLocked.animationResources.find(mLocked.resolvedPointerType);
if (anim_iter != mLocked.animationResources.end()) {
mLocked.animationFrameIndex = 0;
mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -398,7 +385,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
}
mLocked.pointerSprite->setIcon(iter->second);
} else {
- ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
+ ALOGW("Can't find the resource for icon id %d", mLocked.resolvedPointerType);
mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
}
}
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index c0ab58bd2e7e..00dc0854440e 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,18 +43,17 @@ public:
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBounds() const;
void move(float deltaX, float deltaY);
- void setButtonState(int32_t buttonState);
- int32_t getButtonState() const;
void setPosition(float x, float y);
- void getPosition(float* outX, float* outY) const;
+ FloatPoint getPosition() const;
int32_t getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
+ void setStylusHoverMode(bool stylusHoverMode);
- void updatePointerIcon(int32_t iconId);
+ void updatePointerIcon(PointerIconStyle iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
void reloadPointerResources(bool getAdditionalMouseResources);
@@ -74,6 +73,7 @@ private:
struct Locked {
DisplayViewport viewport;
+ bool stylusHoverMode;
size_t animationFrameIndex;
nsecs_t lastFrameUpdatedTime;
@@ -88,18 +88,17 @@ private:
bool resourcesLoaded;
- std::map<int32_t, SpriteIcon> additionalMouseResources;
- std::map<int32_t, PointerAnimation> animationResources;
+ std::map<PointerIconStyle, SpriteIcon> additionalMouseResources;
+ std::map<PointerIconStyle, PointerAnimation> animationResources;
- int32_t requestedPointerType;
-
- int32_t buttonState;
+ PointerIconStyle requestedPointerType;
+ PointerIconStyle resolvedPointerType;
bool animating{false};
} mLocked GUARDED_BY(mLock);
- bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBoundsLocked() const;
void setPositionLocked(float x, float y);
void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 10ea6512c724..88e351963148 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -22,10 +22,18 @@
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
+#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <ftl/enum.h>
+
+#include <mutex>
#include "PointerControllerContext.h"
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
namespace android {
namespace {
@@ -106,16 +114,15 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
- mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
}
std::mutex& PointerController::getLock() const {
return mDisplayInfoListener->mLock;
}
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
- return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+ return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
@@ -129,14 +136,6 @@ void PointerController::move(float deltaX, float deltaY) {
mCursorController.move(transformed.x, transformed.y);
}
-void PointerController::setButtonState(int32_t buttonState) {
- mCursorController.setButtonState(buttonState);
-}
-
-int32_t PointerController::getButtonState() const {
- return mCursorController.getButtonState();
-}
-
void PointerController::setPosition(float x, float y) {
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
@@ -148,15 +147,13 @@ void PointerController::setPosition(float x, float y) {
mCursorController.setPosition(transformed.x, transformed.y);
}
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
const int32_t displayId = mCursorController.getDisplayId();
- mCursorController.getPosition(outX, outY);
+ const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
const auto& transform = getTransformForDisplayLocked(displayId);
- const auto xy = transform.inverse().transform(*outX, *outY);
- *outX = xy.x;
- *outY = xy.y;
+ return FloatPoint{transform.inverse().transform(p.x, p.y)};
}
}
@@ -187,7 +184,11 @@ void PointerController::setPresentation(Presentation presentation) {
return;
}
- if (presentation == Presentation::POINTER) {
+ if (presentation == Presentation::POINTER || presentation == Presentation::STYLUS_HOVER) {
+ // For now, we support stylus hover using the mouse cursor implementation.
+ // TODO: Add proper support for stylus hover icons.
+ mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
+
mCursorController.getAdditionalMouseResources();
clearSpotsLocked();
}
@@ -223,7 +224,7 @@ void PointerController::clearSpots() {
}
void PointerController::clearSpotsLocked() {
- for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ for (auto& [displayId, spotController] : mLocked.spotControllers) {
spotController.clearSpots();
}
}
@@ -235,13 +236,14 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout
void PointerController::reloadPointerResources() {
std::scoped_lock lock(getLock());
- for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ for (auto& [displayId, spotController] : mLocked.spotControllers) {
spotController.reloadSpotResources();
}
if (mCursorController.resourcesLoaded()) {
bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER) {
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
getAdditionalMouseResources = true;
}
mCursorController.reloadPointerResources(getAdditionalMouseResources);
@@ -249,22 +251,35 @@ void PointerController::reloadPointerResources() {
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- std::scoped_lock lock(getLock());
+ struct PointerDisplayChangeArgs {
+ int32_t displayId;
+ FloatPoint cursorPosition;
+ };
+ std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
- bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER) {
- getAdditionalMouseResources = true;
- }
- mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
- if (viewport.displayId != mLocked.pointerDisplayId) {
- float xPos, yPos;
- mCursorController.getPosition(&xPos, &yPos);
- mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
- mLocked.pointerDisplayId = viewport.displayId;
+ { // acquire lock
+ std::scoped_lock lock(getLock());
+
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+ getAdditionalMouseResources = true;
+ }
+ mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ mLocked.pointerDisplayId = viewport.displayId;
+ pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+ }
+ } // release lock
+
+ if (pointerDisplayChanged) {
+ // Notify the policy without holding the pointer controller lock.
+ mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+ pointerDisplayChanged->cursorPosition);
}
}
-void PointerController::updatePointerIcon(int32_t iconId) {
+void PointerController::updatePointerIcon(PointerIconStyle iconId) {
std::scoped_lock lock(getLock());
mCursorController.updatePointerIcon(iconId);
}
@@ -286,13 +301,13 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>&
std::scoped_lock lock(getLock());
for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
- int32_t displayID = it->first;
- if (!displayIdSet.count(displayID)) {
+ int32_t displayId = it->first;
+ if (!displayIdSet.count(displayId)) {
/*
* Ensures that an in-progress animation won't dereference
* a null pointer to TouchSpotController.
*/
- mContext.removeAnimationCallback(displayID);
+ mContext.removeAnimationCallback(displayId);
it = mLocked.spotControllers.erase(it);
} else {
++it;
@@ -313,4 +328,20 @@ const ui::Transform& PointerController::getTransformForDisplayLocked(int display
return it != di.end() ? it->transform : kIdentityTransform;
}
+void PointerController::dump(std::string& dump) {
+ dump += INDENT "PointerController:\n";
+ std::scoped_lock lock(getLock());
+ dump += StringPrintf(INDENT2 "Presentation: %s\n",
+ ftl::enum_string(mLocked.presentation).c_str());
+ dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+ dump += StringPrintf(INDENT2 "Viewports:\n");
+ for (const auto& info : mLocked.mDisplayInfos) {
+ info.dump(dump, INDENT3);
+ }
+ dump += INDENT2 "Spot Controllers:\n";
+ for (const auto& [_, spotController] : mLocked.spotControllers) {
+ spotController.dump(dump, INDENT3);
+ }
+}
+
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index eab030f71e1a..ca14b6e9bfdc 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -27,6 +27,7 @@
#include <map>
#include <memory>
+#include <string>
#include <vector>
#include "MouseCursorController.h"
@@ -49,23 +50,21 @@ public:
~PointerController() override;
- virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
- virtual void move(float deltaX, float deltaY);
- virtual void setButtonState(int32_t buttonState);
- virtual int32_t getButtonState() const;
- virtual void setPosition(float x, float y);
- virtual void getPosition(float* outX, float* outY) const;
- virtual int32_t getDisplayId() const;
- virtual void fade(Transition transition);
- virtual void unfade(Transition transition);
- virtual void setDisplayViewport(const DisplayViewport& viewport);
-
- virtual void setPresentation(Presentation presentation);
- virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId);
- virtual void clearSpots();
-
- void updatePointerIcon(int32_t iconId);
+ std::optional<FloatRect> getBounds() const override;
+ void move(float deltaX, float deltaY) override;
+ void setPosition(float x, float y) override;
+ FloatPoint getPosition() const override;
+ int32_t getDisplayId() const override;
+ void fade(Transition transition) override;
+ void unfade(Transition transition) override;
+ void setDisplayViewport(const DisplayViewport& viewport) override;
+
+ void setPresentation(Presentation presentation) override;
+ void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId) override;
+ void clearSpots() override;
+
+ void updatePointerIcon(PointerIconStyle iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -75,6 +74,8 @@ public:
void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
REQUIRES(getLock());
+ void dump(std::string& dump);
+
protected:
using WindowListenerConsumer =
std::function<void(const sp<android::gui::WindowInfosListener>&)>;
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index c2bc1e020279..f6f5d3bc51bd 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -75,11 +75,13 @@ public:
virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
virtual void loadAdditionalMouseResources(
- std::map<int32_t, SpriteIcon>* outResources,
- std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
- virtual int32_t getDefaultPointerIconId() = 0;
- virtual int32_t getCustomPointerIconId() = 0;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+ std::map<PointerIconStyle, SpriteIcon>* outResources,
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ int32_t displayId) = 0;
+ virtual PointerIconStyle getDefaultPointerIconId() = 0;
+ virtual PointerIconStyle getDefaultStylusIconId() = 0;
+ virtual PointerIconStyle getCustomPointerIconId() = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
};
/*
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 2b809eab4ae4..130b204954b4 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -131,8 +131,9 @@ void SpriteController::doUpdateSprites() {
update.state.surfaceHeight = update.state.icon.height();
update.state.surfaceDrawn = false;
update.state.surfaceVisible = false;
- update.state.surfaceControl = obtainSurface(
- update.state.surfaceWidth, update.state.surfaceHeight);
+ update.state.surfaceControl =
+ obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.displayId);
if (update.state.surfaceControl != NULL) {
update.surfaceChanged = surfaceChanged = true;
}
@@ -168,8 +169,8 @@ 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) {
+ // If surface has changed to a new display, we have to reparent it.
+ if (update.state.dirty & DIRTY_DISPLAY_ID) {
t.reparent(update.state.surfaceControl, mParentSurfaceProvider(update.state.displayId));
needApplyTransaction = true;
}
@@ -242,15 +243,14 @@ void SpriteController::doUpdateSprites() {
&& (becomingVisible
|| (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
Parcel p;
- p.writeInt32(update.state.icon.style);
+ p.writeInt32(static_cast<int32_t>(update.state.icon.style));
p.writeFloat(update.state.icon.hotSpotX);
p.writeFloat(update.state.icon.hotSpotY);
// Pass cursor metadata in the sprite surface so that when Android is running as a
// client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
// update mouse cursor in the host OS.
- t.setMetadata(
- update.state.surfaceControl, METADATA_MOUSE_CURSOR, p);
+ t.setMetadata(update.state.surfaceControl, gui::METADATA_MOUSE_CURSOR, p);
}
int32_t surfaceLayer = mOverlayLayer + update.state.layer;
@@ -331,21 +331,28 @@ void SpriteController::ensureSurfaceComposerClient() {
}
}
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
+ int32_t displayId) {
ensureSurfaceComposerClient();
- sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
- String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eHidden |
- ISurfaceComposerClient::eCursorWindow);
- if (surfaceControl == NULL || !surfaceControl->isValid()) {
+ const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
+ if (parent == nullptr) {
+ ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+ }
+
+ const sp<SurfaceControl> surfaceControl =
+ mSurfaceComposerClient->createSurface(String8("Sprite"), width, height,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden |
+ ISurfaceComposerClient::eCursorWindow,
+ parent ? parent->getHandle() : nullptr);
+ if (surfaceControl == nullptr || !surfaceControl->isValid()) {
ALOGE("Error creating sprite surface.");
- return NULL;
+ return nullptr;
}
return surfaceControl;
}
-
// --- SpriteController::SpriteImpl ---
SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 2e9cb9685c46..1f113c045360 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -265,7 +265,7 @@ private:
void doDisposeSurfaces();
void ensureSurfaceComposerClient();
- sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId);
};
} // namespace android
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index a257d7e89ebc..5f085bbd2374 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -19,6 +19,7 @@
#include <android/graphics/bitmap.h>
#include <gui/Surface.h>
+#include <input/Input.h>
namespace android {
@@ -26,12 +27,13 @@ namespace android {
* Icon that a sprite displays, including its hotspot.
*/
struct SpriteIcon {
- inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) {}
- inline SpriteIcon(const graphics::Bitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY)
+ inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {}
+ inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
+ float hotSpotY)
: bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) {}
graphics::Bitmap bitmap;
- int32_t style;
+ PointerIconStyle style;
float hotSpotX;
float hotSpotY;
@@ -41,7 +43,7 @@ struct SpriteIcon {
inline void reset() {
bitmap.reset();
- style = 0;
+ style = PointerIconStyle::TYPE_NULL;
hotSpotX = 0;
hotSpotY = 0;
}
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index f7c685ff8ba6..d9fe5996bcff 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -21,13 +21,14 @@
#include "TouchSpotController.h"
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <log/log.h>
-#include <SkBitmap.h>
-#include <SkBlendMode.h>
-#include <SkCanvas.h>
-#include <SkColor.h>
-#include <SkPaint.h>
+#include <mutex>
+
+#define INDENT " "
+#define INDENT2 " "
namespace {
// Time to spend fading out the spot completely.
@@ -59,6 +60,12 @@ void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, fl
}
}
+void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const {
+ out += prefix;
+ base::StringAppendF(&out, "Spot{id=%" PRIx32 ", alpha=%f, scale=%f, pos=[%f, %f]}\n", id, alpha,
+ scale, x, y);
+}
+
// --- TouchSpotController ---
TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
@@ -261,4 +268,22 @@ void TouchSpotController::startAnimationLocked() REQUIRES(mLock) {
mContext.addAnimationCallback(mDisplayId, func);
}
+void TouchSpotController::dump(std::string& out, const char* prefix) const {
+ using base::StringAppendF;
+ out += prefix;
+ out += "SpotController:\n";
+ out += prefix;
+ StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+ std::scoped_lock lock(mLock);
+ out += prefix;
+ StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
+ out += prefix;
+ out += INDENT "Spots:\n";
+ std::string spotPrefix = prefix;
+ spotPrefix += INDENT2;
+ for (const auto& spot : mLocked.displaySpots) {
+ spot->dump(out, spotPrefix.c_str());
+ }
+}
+
} // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 703de3603f48..5bbc75d9570b 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -38,6 +38,8 @@ public:
void reloadSpotResources();
bool doAnimations(nsecs_t timestamp);
+ void dump(std::string& out, const char* prefix = "") const;
+
private:
struct Spot {
static const uint32_t INVALID_ID = 0xffffffff;
@@ -58,6 +60,7 @@ private:
mLastIcon(nullptr) {}
void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+ void dump(std::string& out, const char* prefix = "") const;
private:
const SpriteIcon* mLastIcon;
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index f9752ed155df..2378d42793a1 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -14,17 +14,18 @@
* limitations under the License.
*/
-#include "mocks/MockSprite.h"
-#include "mocks/MockSpriteController.h"
-
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
#include <input/PointerController.h>
#include <input/SpriteController.h>
#include <atomic>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
#include <thread>
+#include "input/Input.h"
+#include "mocks/MockSprite.h"
+#include "mocks/MockSpriteController.h"
+
namespace android {
enum TestCursorType {
@@ -34,12 +35,12 @@ enum TestCursorType {
CURSOR_TYPE_ANCHOR,
CURSOR_TYPE_ADDITIONAL,
CURSOR_TYPE_ADDITIONAL_ANIM,
+ CURSOR_TYPE_STYLUS,
CURSOR_TYPE_CUSTOM = -1,
};
using ::testing::AllOf;
using ::testing::Field;
-using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Test;
@@ -52,11 +53,14 @@ class MockPointerControllerPolicyInterface : public PointerControllerPolicyInter
public:
virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
- virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
- std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
- virtual int32_t getDefaultPointerIconId() override;
- virtual int32_t getCustomPointerIconId() override;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+ virtual void loadAdditionalMouseResources(
+ std::map<PointerIconStyle, SpriteIcon>* outResources,
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ int32_t displayId) override;
+ virtual PointerIconStyle getDefaultPointerIconId() override;
+ virtual PointerIconStyle getDefaultStylusIconId() override;
+ virtual PointerIconStyle getCustomPointerIconId() override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
@@ -85,34 +89,42 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources
}
void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
- std::map<int32_t, SpriteIcon>* outResources,
- std::map<int32_t, PointerAnimation>* outAnimationResources,
- int32_t) {
+ std::map<PointerIconStyle, SpriteIcon>* outResources,
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
SpriteIcon icon;
PointerAnimation anim;
// CURSOR_TYPE_ADDITIONAL doesn't have animation resource.
int32_t cursorType = CURSOR_TYPE_ADDITIONAL;
loadPointerIconForType(&icon, cursorType);
- (*outResources)[cursorType] = icon;
+ (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
// CURSOR_TYPE_ADDITIONAL_ANIM has animation resource.
cursorType = CURSOR_TYPE_ADDITIONAL_ANIM;
loadPointerIconForType(&icon, cursorType);
anim.animationFrames.push_back(icon);
anim.durationPerFrame = 10;
- (*outResources)[cursorType] = icon;
- (*outAnimationResources)[cursorType] = anim;
+ (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
+ (*outAnimationResources)[static_cast<PointerIconStyle>(cursorType)] = anim;
+
+ // CURSOR_TYPE_STYLUS doesn't have animation resource.
+ cursorType = CURSOR_TYPE_STYLUS;
+ loadPointerIconForType(&icon, cursorType);
+ (*outResources)[static_cast<PointerIconStyle>(cursorType)] = icon;
additionalMouseResourcesLoaded = true;
}
-int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
- return CURSOR_TYPE_DEFAULT;
+PointerIconStyle MockPointerControllerPolicyInterface::getDefaultPointerIconId() {
+ return static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT);
+}
+
+PointerIconStyle MockPointerControllerPolicyInterface::getDefaultStylusIconId() {
+ return static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS);
}
-int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() {
- return CURSOR_TYPE_CUSTOM;
+PointerIconStyle MockPointerControllerPolicyInterface::getCustomPointerIconId() {
+ return static_cast<PointerIconStyle>(CURSOR_TYPE_CUSTOM);
}
bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() {
@@ -124,15 +136,15 @@ bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() {
}
void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) {
- icon->style = type;
+ icon->style = static_cast<PointerIconStyle>(type);
std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type);
icon->hotSpotX = hotSpot.first;
icon->hotSpotY = hotSpot.second;
}
void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
- float /*xPos*/,
- float /*yPos*/) {
+ const FloatPoint& /*position*/
+) {
latestPointerDisplayId = displayId;
}
@@ -205,11 +217,26 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT);
EXPECT_CALL(*mPointerSprite, setVisible(true));
EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
- EXPECT_CALL(*mPointerSprite, setIcon(
- AllOf(
- Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT),
- Field(&SpriteIcon::hotSpotX, hotspot.first),
- Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style,
+ static_cast<PointerIconStyle>(CURSOR_TYPE_DEFAULT)),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->reloadPointerResources();
+}
+
+TEST_F(PointerControllerTest, useStylusTypeForStylusHover) {
+ ensureDisplayViewportIsSet();
+ mPointerController->setPresentation(PointerController::Presentation::STYLUS_HOVER);
+ mPointerController->unfade(PointerController::Transition::IMMEDIATE);
+ std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_STYLUS);
+ EXPECT_CALL(*mPointerSprite, setVisible(true));
+ EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style,
+ static_cast<PointerIconStyle>(CURSOR_TYPE_STYLUS)),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
mPointerController->reloadPointerResources();
}
@@ -222,12 +249,11 @@ TEST_F(PointerControllerTest, updatePointerIcon) {
std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
EXPECT_CALL(*mPointerSprite, setVisible(true));
EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
- EXPECT_CALL(*mPointerSprite, setIcon(
- AllOf(
- Field(&SpriteIcon::style, type),
- Field(&SpriteIcon::hotSpotX, hotspot.first),
- Field(&SpriteIcon::hotSpotY, hotspot.second))));
- mPointerController->updatePointerIcon(type);
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
+ Field(&SpriteIcon::hotSpotX, hotspot.first),
+ Field(&SpriteIcon::hotSpotY, hotspot.second))));
+ mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
}
TEST_F(PointerControllerTest, setCustomPointerIcon) {
@@ -239,17 +265,16 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) {
float hotSpotY = 20;
SpriteIcon icon;
- icon.style = style;
+ icon.style = static_cast<PointerIconStyle>(style);
icon.hotSpotX = hotSpotX;
icon.hotSpotY = hotSpotY;
EXPECT_CALL(*mPointerSprite, setVisible(true));
EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
- EXPECT_CALL(*mPointerSprite, setIcon(
- AllOf(
- Field(&SpriteIcon::style, style),
- Field(&SpriteIcon::hotSpotX, hotSpotX),
- Field(&SpriteIcon::hotSpotY, hotSpotY))));
+ EXPECT_CALL(*mPointerSprite,
+ setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(style)),
+ Field(&SpriteIcon::hotSpotX, hotSpotX),
+ Field(&SpriteIcon::hotSpotY, hotSpotY))));
mPointerController->setCustomPointerIcon(icon);
}
diff --git a/libs/securebox/Android.bp b/libs/securebox/Android.bp
new file mode 100644
index 000000000000..a29c03cfdcca
--- /dev/null
+++ b/libs/securebox/Android.bp
@@ -0,0 +1,8 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+ name: "securebox",
+ srcs: ["src/**/*.java"],
+}
diff --git a/libs/securebox/OWNERS b/libs/securebox/OWNERS
new file mode 100644
index 000000000000..e160799aa10d
--- /dev/null
+++ b/libs/securebox/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
diff --git a/libs/securebox/src/com/android/security/SecureBox.java b/libs/securebox/src/com/android/security/SecureBox.java
new file mode 100644
index 000000000000..0ebaff4ac8e5
--- /dev/null
+++ b/libs/securebox/src/com/android/security/SecureBox.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Implementation of the SecureBox v2 crypto functions.
+ *
+ * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
+ * credential types:
+ *
+ * <ul>
+ * <li>A public key owned by the recipient,
+ * <li>A secret shared between the sender and the recipient, or
+ * <li>Both a recipient's public key and a shared secret.
+ * </ul>
+ *
+ * @hide
+ */
+public class SecureBox {
+
+ private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
+ private static final byte[] HKDF_SALT =
+ ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+ private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
+ "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
+ "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] CONSTANT_01 = {(byte) 0x01};
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
+
+ private static final String CIPHER_ALG = "AES";
+ private static final String EC_ALG = "EC";
+ private static final String EC_P256_COMMON_NAME = "secp256r1";
+ private static final String EC_P256_OPENSSL_NAME = "prime256v1";
+ private static final String ENC_ALG = "AES/GCM/NoPadding";
+ private static final String KA_ALG = "ECDH";
+ private static final String MAC_ALG = "HmacSHA256";
+
+ private static final int EC_COORDINATE_LEN_BYTES = 32;
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
+ private static final int GCM_NONCE_LEN_BYTES = 12;
+ private static final int GCM_KEY_LEN_BYTES = 16;
+ private static final int GCM_TAG_LEN_BYTES = 16;
+
+ private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
+
+ private enum AesGcmOperation {
+ ENCRYPT,
+ DECRYPT
+ }
+
+ // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
+ private static final BigInteger EC_PARAM_P =
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+ private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
+ private static final BigInteger EC_PARAM_B =
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+
+ @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
+
+ static {
+ EllipticCurve curveSpec =
+ new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
+ ECPoint generator =
+ new ECPoint(
+ new BigInteger(
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ 16),
+ new BigInteger(
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+ 16));
+ BigInteger generatorOrder =
+ new BigInteger(
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+ EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
+ }
+
+ private SecureBox() {}
+
+ /**
+ * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
+ * {@link #decrypt}.
+ *
+ * @return the randomly generated public-key pair
+ * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
+ * @hide
+ */
+ public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
+ try {
+ // Try using the OpenSSL provider first
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ // Try another name for NIST P-256
+ }
+ try {
+ keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
+ return keyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
+ }
+ }
+
+ /**
+ * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
+ * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
+ *
+ * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
+ * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
+ *
+ * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
+ * only with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload is to be encrypted only with the recipient's public key
+ * @param header the data that will be authenticated with {@code payload} but not encrypted, or
+ * null if the data is empty
+ * @param payload the data to be encrypted, or null if the data is empty
+ * @return the encrypted payload
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+ * @hide
+ */
+ public static byte[] encrypt(
+ @Nullable PublicKey theirPublicKey,
+ @Nullable byte[] sharedSecret,
+ @Nullable byte[] header,
+ @Nullable byte[] payload)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (theirPublicKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the public key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ payload = emptyByteArrayIfNull(payload);
+
+ KeyPair senderKeyPair;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (theirPublicKey == null) {
+ senderKeyPair = null;
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderKeyPair = genKeyPair();
+ dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = genRandomNonce();
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
+ SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
+ if (senderKeyPair == null) {
+ return ArrayUtils.concat(VERSION, randNonce, ciphertext);
+ } else {
+ return ArrayUtils.concat(
+ VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
+ }
+ }
+
+ /**
+ * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
+ * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
+ * {@code sharedSecret} is equivalent to null.
+ *
+ * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
+ * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
+ * AEADBadTagException} will be thrown.
+ *
+ * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
+ * with the shared secret
+ * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+ * payload was encrypted only with the recipient's public key
+ * @param header the data that was authenticated with the original payload but not encrypted, or
+ * null if the data is empty
+ * @param encryptedPayload the data to be decrypted
+ * @return the original payload that was encrypted
+ * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+ * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+ * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
+ * cannot be validated, or if the payload is not a valid SecureBox V2 payload.
+ * @hide
+ */
+ public static byte[] decrypt(
+ @Nullable PrivateKey ourPrivateKey,
+ @Nullable byte[] sharedSecret,
+ @Nullable byte[] header,
+ byte[] encryptedPayload)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ sharedSecret = emptyByteArrayIfNull(sharedSecret);
+ if (ourPrivateKey == null && sharedSecret.length == 0) {
+ throw new IllegalArgumentException("Both the private key and shared secret are empty");
+ }
+ header = emptyByteArrayIfNull(header);
+ if (encryptedPayload == null) {
+ throw new NullPointerException("Encrypted payload must not be null.");
+ }
+
+ ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
+ byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
+ if (!Arrays.equals(version, VERSION)) {
+ throw new AEADBadTagException("The payload was not encrypted by SecureBox v2");
+ }
+
+ byte[] senderPublicKeyBytes;
+ byte[] dhSecret;
+ byte[] hkdfInfo;
+ if (ourPrivateKey == null) {
+ dhSecret = EMPTY_BYTE_ARRAY;
+ hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+ } else {
+ senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
+ dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
+ hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+ }
+
+ byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
+ byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
+ SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+ return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
+ }
+
+ private static byte[] readEncryptedPayload(ByteBuffer buffer, int length)
+ throws AEADBadTagException {
+ byte[] output = new byte[length];
+ try {
+ buffer.get(output);
+ } catch (BufferUnderflowException ex) {
+ throw new AEADBadTagException("The encrypted payload is too short");
+ }
+ return output;
+ }
+
+ private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
+ try {
+ agreement.init(ourPrivateKey);
+ } catch (RuntimeException ex) {
+ // Rethrow the RuntimeException as InvalidKeyException
+ throw new InvalidKeyException(ex);
+ }
+ agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
+ return agreement.generateSecret();
+ }
+
+ /** Derives a 128-bit AES key. */
+ private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
+ throws NoSuchAlgorithmException {
+ Mac mac = Mac.getInstance(MAC_ALG);
+ try {
+ mac.init(new SecretKeySpec(salt, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ byte[] pseudorandomKey = mac.doFinal(secret);
+
+ try {
+ mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
+ } catch (InvalidKeyException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ mac.update(info);
+ // Hashing just one block will yield 256 bits, which is enough to construct the AES key
+ byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
+
+ return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
+ }
+
+ private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ try {
+ return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
+ } catch (AEADBadTagException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
+ }
+
+ private static byte[] aesGcmInternal(
+ AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
+ throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(ENC_ALG);
+ } catch (NoSuchPaddingException ex) {
+ // This should never happen because AES-GCM doesn't use padding
+ throw new RuntimeException(ex);
+ }
+ GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
+ try {
+ if (operation == AesGcmOperation.DECRYPT) {
+ cipher.init(Cipher.DECRYPT_MODE, key, spec);
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, key, spec);
+ }
+ } catch (InvalidAlgorithmParameterException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ try {
+ cipher.updateAAD(aad);
+ return cipher.doFinal(text);
+ } catch (AEADBadTagException ex) {
+ // Catch and rethrow AEADBadTagException first because it's a subclass of
+ // BadPaddingException
+ throw ex;
+ } catch (IllegalBlockSizeException | BadPaddingException ex) {
+ // This should never happen because AES-GCM can handle inputs of any length without
+ // padding
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Encodes public key in format expected by the secure hardware module. This is used as part
+ * of the vault params.
+ *
+ * @param publicKey The public key.
+ * @return The key packed into a 65-byte array.
+ */
+ public static byte[] encodePublicKey(PublicKey publicKey) {
+ ECPoint point = ((ECPublicKey) publicKey).getW();
+ byte[] x = point.getAffineX().toByteArray();
+ byte[] y = point.getAffineY().toByteArray();
+
+ byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
+ // The order of arraycopy() is important, because the coordinates may have a one-byte
+ // leading 0 for the sign bit of two's complement form
+ System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
+ System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
+ output[0] = EC_PUBLIC_KEY_PREFIX;
+ return output;
+ }
+
+ /**
+ * Decodes byte[] encoded public key.
+ *
+ * @param keyBytes encoded public key
+ * @return the public key
+ */
+ public static PublicKey decodePublicKey(byte[] keyBytes)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ BigInteger x =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
+ BigInteger y =
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(
+ keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
+
+ // Checks if the point is indeed on the P-256 curve for security considerations
+ validateEcPoint(x, y);
+
+ KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
+ try {
+ return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
+ } catch (InvalidKeySpecException ex) {
+ // This should never happen
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
+ if (x.compareTo(EC_PARAM_P) >= 0
+ || y.compareTo(EC_PARAM_P) >= 0
+ || x.signum() == -1
+ || y.signum() == -1) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+
+ // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
+ BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
+ BigInteger rhs =
+ x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
+ .add(EC_PARAM_A) // x^2 + a
+ .mod(EC_PARAM_P) // This will speed up the next multiplication
+ .multiply(x) // (x^2 + a) * x = x^3 + ax
+ .add(EC_PARAM_B) // x^3 + ax + b
+ .mod(EC_PARAM_P);
+ if (!lhs.equals(rhs)) {
+ throw new InvalidKeyException("Point lies outside of the expected curve");
+ }
+ }
+
+ private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
+ byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
+ new SecureRandom().nextBytes(nonce);
+ return nonce;
+ }
+
+ private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
+ return input == null ? EMPTY_BYTE_ARRAY : input;
+ }
+}
diff --git a/libs/securebox/tests/Android.bp b/libs/securebox/tests/Android.bp
new file mode 100644
index 000000000000..7df546ae0ff6
--- /dev/null
+++ b/libs/securebox/tests/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "SecureBoxTests",
+ srcs: [
+ "**/*.java",
+ ],
+ static_libs: [
+ "securebox",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "frameworks-base-testutils",
+ "junit",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "testables",
+ "testng",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+}
diff --git a/libs/securebox/tests/AndroidManifest.xml b/libs/securebox/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..3dc956394362
--- /dev/null
+++ b/libs/securebox/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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="com.android.security.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 SecureBox"
+ android:targetPackage="com.android.security.tests">
+ </instrumentation>
+
+</manifest>
diff --git a/libs/securebox/tests/AndroidTest.xml b/libs/securebox/tests/AndroidTest.xml
new file mode 100644
index 000000000000..54abd13515b4
--- /dev/null
+++ b/libs/securebox/tests/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 SecureBox">
+ <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="SecureBoxTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="SecureBoxTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.security.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/securebox/tests/src/com/android/security/SecureBoxTest.java b/libs/securebox/tests/src/com/android/security/SecureBoxTest.java
new file mode 100644
index 000000000000..b6e2365038dc
--- /dev/null
+++ b/libs/securebox/tests/src/com/android/security/SecureBoxTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+
+import javax.crypto.AEADBadTagException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecureBoxTest {
+
+ private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
+ private static final int NUM_TEST_ITERATIONS = 100;
+ private static final int VERSION_LEN_BYTES = 2;
+
+ // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
+ // cross-verify the two implementations.
+ private static final byte[] VAULT_PARAMS =
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98,
+ (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe,
+ (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01,
+ (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61,
+ (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f,
+ (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
+ (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a,
+ (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2,
+ (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, (byte) 0x31,
+ (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00
+ };
+ private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
+ private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
+ private static final byte[] ENCRYPTED_RECOVERY_KEY =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
+ (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
+ (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
+ (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
+ (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
+ (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
+ (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
+ (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
+ (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
+ (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
+ (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
+ (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
+ (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
+ (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
+ (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
+ (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
+ (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
+ (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
+ (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
+ (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
+ };
+ private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
+ private static final byte[] RECOVERY_CLAIM =
+ new byte[] {
+ (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
+ (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
+ (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
+ (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
+ (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
+ (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
+ (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
+ (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
+ (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
+ (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
+ (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
+ (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
+ (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
+ (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
+ (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
+ (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
+ (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
+ (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
+ (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
+ (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
+ (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
+ (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
+ (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
+ (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
+ };
+
+ private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
+ private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
+ private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
+
+ private static final PublicKey THM_PUBLIC_KEY;
+ private static final PrivateKey THM_PRIVATE_KEY;
+
+ static {
+ try {
+ THM_PUBLIC_KEY =
+ SecureBox.decodePublicKey(
+ new byte[] {
+ (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18,
+ (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4,
+ (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c,
+ (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a,
+ (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0,
+ (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
+ (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79,
+ (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0,
+ (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32,
+ (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc,
+ (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
+ (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa
+ });
+ THM_PRIVATE_KEY =
+ decodePrivateKey(
+ new byte[] {
+ (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
+ (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
+ (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
+ (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
+ (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
+ (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
+ (byte) 0x77, (byte) 0x01
+ });
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Test
+ public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
+ KeyPair keyPair1 = SecureBox.genKeyPair();
+ KeyPair keyPair2 = SecureBox.genKeyPair();
+ assertThat(keyPair1).isNotEqualTo(keyPair2);
+ }
+
+ @Test
+ public void decryptRecoveryClaim() throws Exception {
+ byte[] claimContent =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ /*sharedSecret=*/ null,
+ ArrayUtils.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+ RECOVERY_CLAIM);
+ assertThat(claimContent).isEqualTo(ArrayUtils.concat(THM_KF_HASH, KEY_CLAIMANT));
+ }
+
+ @Test
+ public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ THM_KF_HASH,
+ ArrayUtils.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+ ENCRYPTED_RECOVERY_KEY);
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ byte[] state = TEST_PAYLOAD;
+ // Iterate multiple times to amplify any errors
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+ }
+ assertThat(state).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullSharedSecret() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullHeader() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
+ byte[] decrypted =
+ SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
+ assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+ }
+
+ @Test
+ public void encryptThenDecrypt_nullPayload() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(
+ THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
+ byte[] decrypted =
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ encrypted);
+ assertThat(decrypted.length).isEqualTo(0);
+ }
+
+ @Test
+ public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.encrypt(
+ /*theirPublicKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("public key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
+ IllegalArgumentException expected =
+ expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ SecureBox.decrypt(
+ /*ourPrivateKey=*/ null,
+ /*sharedSecret=*/ null,
+ TEST_HEADER,
+ TEST_PAYLOAD));
+ assertThat(expected.getMessage()).contains("private key and shared secret");
+ }
+
+ @Test
+ public void decrypt_nullEncryptedPayload() throws Exception {
+ NullPointerException expected =
+ expectThrows(
+ NullPointerException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ /*encryptedPayload=*/ null));
+ assertThat(expected.getMessage()).contains("payload");
+ }
+
+ @Test
+ public void decrypt_badAuthenticationTag() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ encrypted[encrypted.length - 1] ^= (byte) 1;
+
+ assertThrows(
+ AEADBadTagException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void encrypt_invalidPublicKey() throws Exception {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PublicKey publicKey = keyGen.genKeyPair().getPublic();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
+ }
+
+ @Test
+ public void decrypt_invalidPrivateKey() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
+
+ assertThrows(
+ InvalidKeyException.class,
+ () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+ }
+
+ @Test
+ public void decrypt_publicKeyOutsideCurve() throws Exception {
+ byte[] encrypted =
+ SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+ // Flip the least significant bit of the encoded public key
+ encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
+
+ InvalidKeyException expected =
+ expectThrows(
+ InvalidKeyException.class,
+ () ->
+ SecureBox.decrypt(
+ THM_PRIVATE_KEY,
+ TEST_SHARED_SECRET,
+ TEST_HEADER,
+ encrypted));
+ assertThat(expected.getMessage()).contains("expected curve");
+ }
+
+ @Test
+ public void encodeThenDecodePublicKey() throws Exception {
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ PublicKey originalKey = SecureBox.genKeyPair().getPublic();
+ byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
+ PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
+ assertThat(originalKey).isEqualTo(decodedKey);
+ }
+ }
+
+ private static byte[] getBytes(String str) {
+ return str.getBytes(StandardCharsets.UTF_8);
+ }
+
+ private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
+ assertThat(keyBytes.length).isEqualTo(32);
+ BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
+ }
+}
diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
index 18cfce528205..c019a8ce0b44 100644
--- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
+++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
@@ -83,7 +83,9 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit
super.onCreate(savedInstanceState);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
- mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ mPermissionIntent = PendingIntent.getBroadcast(this, 0,
+ new Intent(ACTION_USB_PERMISSION).setPackage(this.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);