summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java67
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java138
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java388
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java137
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java103
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java35
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java31
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java39
-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.java201
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java212
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java21
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java26
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java12
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java21
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java32
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java194
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java40
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java29
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java35
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java232
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java204
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin30713 -> 32970 bytes
-rw-r--r--libs/WindowManager/Shell/Android.bp22
-rw-r--r--libs/WindowManager/Shell/res/color/decor_title_color.xml (renamed from libs/WindowManager/Shell/res/color/decor_caption_title_color.xml)0
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_floating_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_more_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml12
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml (renamed from libs/WindowManager/Shell/res/drawable/decor_caption_title.xml)5
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/handle_menu_background.xml30
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml41
-rw-r--r--libs/WindowManager/Shell/res/layout/caption_window_decoration.xml55
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml49
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml42
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml25
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml19
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml18
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml21
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml17
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml17
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml17
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml69
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings_tv.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml15
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings_tv.xml14
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml14
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml17
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml18
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java264
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java183
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java132
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java226
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl)18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java)39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java238
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt122
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt314
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java259
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java455
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java687
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java385
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java230
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java191
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java424
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java700
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java257
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java375
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java260
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java217
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java616
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java447
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java158
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java245
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java136
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java400
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt408
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java248
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java36
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java183
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java45
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java49
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java260
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java186
-rw-r--r--libs/hwui/Android.bp2
365 files changed, 11286 insertions, 8032 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index cc2bb63ca8e1..0bdf98c8680f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -155,6 +155,7 @@ public final class DeviceStateManagerFoldingFeatureProducer
* Adds the data to the storeFeaturesConsumer when the data is ready.
* @param storeFeaturesConsumer a consumer to collect the data when it is first available.
*/
+ @Override
public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
mRawFoldSupplier.getData((String displayFeaturesString) -> {
if (TextUtils.isEmpty(displayFeaturesString)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index fb0a9db6a20b..54edd9ec4335 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -17,9 +17,13 @@
package androidx.window.extensions;
import android.app.ActivityThread;
+import android.app.Application;
import android.content.Context;
+import android.window.TaskFragmentOrganizer;
import androidx.annotation.NonNull;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.extensions.area.WindowAreaComponent;
import androidx.window.extensions.area.WindowAreaComponentImpl;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
@@ -27,6 +31,8 @@ import androidx.window.extensions.embedding.SplitController;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import java.util.Objects;
+
/**
* The reference implementation of {@link WindowExtensions} that implements the initial API version.
@@ -34,7 +40,8 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl;
public class WindowExtensionsImpl implements WindowExtensions {
private final Object mLock = new Object();
- private volatile WindowLayoutComponent mWindowLayoutComponent;
+ private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
+ private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
private volatile SplitController mSplitController;
private volatile WindowAreaComponent mWindowAreaComponent;
@@ -44,6 +51,49 @@ public class WindowExtensionsImpl implements WindowExtensions {
return 1;
}
+ @NonNull
+ private Application getApplication() {
+ return Objects.requireNonNull(ActivityThread.currentApplication());
+ }
+
+ @NonNull
+ private DeviceStateManagerFoldingFeatureProducer getFoldingFeatureProducer() {
+ if (mFoldingFeatureProducer == null) {
+ synchronized (mLock) {
+ if (mFoldingFeatureProducer == null) {
+ Context context = getApplication();
+ RawFoldingFeatureProducer foldingFeatureProducer =
+ new RawFoldingFeatureProducer(context);
+ mFoldingFeatureProducer =
+ new DeviceStateManagerFoldingFeatureProducer(context,
+ foldingFeatureProducer);
+ }
+ }
+ }
+ return mFoldingFeatureProducer;
+ }
+
+ @NonNull
+ private WindowLayoutComponentImpl getWindowLayoutComponentImpl() {
+ if (mWindowLayoutComponent == null) {
+ synchronized (mLock) {
+ if (mWindowLayoutComponent == null) {
+ Context context = getApplication();
+ DeviceStateManagerFoldingFeatureProducer producer =
+ getFoldingFeatureProducer();
+ // TODO(b/263263909) Use the organizer to tell if an Activity is embededed.
+ // Need to improve our Dependency Injection and centralize the logic.
+ TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> {
+ throw new RuntimeException("Not allowed!");
+ });
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer,
+ producer);
+ }
+ }
+ }
+ return mWindowLayoutComponent;
+ }
+
/**
* Returns a reference implementation of {@link WindowLayoutComponent} if available,
* {@code null} otherwise. The implementation must match the API level reported in
@@ -52,15 +102,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
*/
@Override
public WindowLayoutComponent getWindowLayoutComponent() {
- if (mWindowLayoutComponent == null) {
- synchronized (mLock) {
- if (mWindowLayoutComponent == null) {
- Context context = ActivityThread.currentApplication();
- mWindowLayoutComponent = new WindowLayoutComponentImpl(context);
- }
- }
- }
- return mWindowLayoutComponent;
+ return getWindowLayoutComponentImpl();
}
/**
@@ -74,7 +116,10 @@ public class WindowExtensionsImpl implements WindowExtensions {
if (mSplitController == null) {
synchronized (mLock) {
if (mSplitController == null) {
- mSplitController = new SplitController();
+ mSplitController = new SplitController(
+ getWindowLayoutComponentImpl(),
+ getFoldingFeatureProducer()
+ );
}
}
}
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 74303e2fab7c..87fa63d7fe14 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,13 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
@@ -25,13 +32,14 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -79,26 +87,20 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
@Override
public void unregisterOrganizer() {
if (mAnimationController != null) {
- mAnimationController.unregisterAllRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer();
}
- /** Overrides the animation if the transition is on the given Task. */
- void startOverrideSplitAnimation(int taskId) {
+ /**
+ * Overrides the animation for transitions of embedded activities organized by this organizer.
+ */
+ void overrideSplitAnimation() {
if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this);
}
- mAnimationController.registerRemoteAnimations(taskId);
- }
-
- /** No longer overrides the animation if the transition is on the given Task. */
- @GuardedBy("mLock")
- void stopOverrideSplitAnimation(int taskId) {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations(taskId);
- }
+ mAnimationController.registerRemoteAnimations();
}
/**
@@ -115,13 +117,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* @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 Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
@NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
- @WindowingMode int windowingMode) {
+ @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
@@ -132,14 +135,28 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
launchingFragmentBounds, windowingMode, launchingActivity);
}
+ updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
// Create a TaskFragment for the secondary activity.
- createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
- secondaryFragmentBounds, windowingMode, activityIntent,
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), secondaryFragmentToken, ownerToken)
+ .setInitialBounds(secondaryFragmentBounds)
+ .setWindowingMode(windowingMode)
+ // Make sure to set the paired fragment token so that the new TaskFragment will be
+ // positioned right above the paired TaskFragment.
+ // This is needed in case we need to launch a placeholder Activity to split below a
+ // transparent always-expand Activity.
+ .setPairedPrimaryFragmentToken(launchingFragmentToken)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ updateAnimationParams(wct, secondaryFragmentToken, splitAttributes);
+ wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
+ false /* isStacked */);
}
/**
@@ -152,6 +169,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -164,6 +182,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -172,8 +191,21 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), fragmentToken, ownerToken)
+ .setInitialBounds(bounds)
+ .setWindowingMode(windowingMode)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ }
+
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
+ throw new IllegalArgumentException(
+ "There is an existing TaskFragment with fragmentToken="
+ + fragmentOptions.getFragmentToken());
+ }
wct.createTaskFragment(fragmentOptions);
}
@@ -188,18 +220,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
- /**
- * @param ownerToken The token of the activity that creates this task fragment. It does not
- * have to be a child of this task fragment, but must belong to the same task.
- */
- private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
- @WindowingMode int windowingMode, @NonNull Intent activityIntent,
- @Nullable Bundle activityOptions) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
- wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
- }
-
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
@@ -215,20 +235,26 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
- TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- if (mFragmentInfos.containsKey(fragmentToken)) {
- throw new IllegalArgumentException(
- "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
+ boolean isStacked) {
+ final boolean finishPrimaryWithSecondary;
+ if (isStacked) {
+ finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishPrimaryWithSecondaryBehavior(splitRule));
+ } else {
+ finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
}
+ wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
- return new TaskFragmentCreationParams.Builder(
- getOrganizerToken(),
- fragmentToken,
- ownerToken)
- .setInitialBounds(bounds)
- .setWindowingMode(windowingMode)
- .build();
+ final boolean finishSecondaryWithPrimary;
+ if (isStacked) {
+ finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishSecondaryWithPrimaryBehavior(splitRule));
+ } else {
+ finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
+ }
+ wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@@ -252,6 +278,24 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
+ /**
+ * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on
+ * {@link SplitAttributes}.
+ */
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) {
+ updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes));
+ }
+
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ wct.setTaskFragmentOperation(fragmentToken, operation);
+ }
+
void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
@@ -273,4 +317,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
mCallback.onTransactionReady(transaction);
}
+
+ private static TaskFragmentAnimationParams createAnimationParamsOrDefault(
+ @Nullable SplitAttributes splitAttributes) {
+ if (splitAttributes == null) {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .build();
+ }
}
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 00be5a6e3416..77284c4166bd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -109,6 +109,12 @@ class SplitContainer {
return (mSplitRule instanceof SplitPlaceholderRule);
}
+ @NonNull
+ SplitInfo toSplitInfo() {
+ return new SplitInfo(mPrimaryContainer.toActivityStack(),
+ mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ }
+
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
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 203ece091e46..1cd3ea5592e3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -20,10 +20,11 @@ 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.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.getTransitionType;
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;
@@ -40,7 +41,6 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -65,6 +65,7 @@ import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
@@ -74,8 +75,9 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,9 +102,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
final SplitPresenter mPresenter;
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ final TransactionManager mTransactionManager;
+
// Currently applied split configuration.
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
+
/**
* A developer-defined {@link SplitAttributes} calculator to compute the current
* {@link SplitAttributes} with the current device and window states.
@@ -120,6 +127,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@Nullable
private SplitAttributesCalculator mSplitAttributesCalculator;
+
/**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
@@ -137,19 +145,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
- @NonNull
- final WindowLayoutComponentImpl mWindowLayoutComponent;
-
- public SplitController() {
- this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider
- .getWindowExtensions().getWindowLayoutComponent()));
- }
- @VisibleForTesting
- SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) {
+ public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
+ @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
- mPresenter = new SplitPresenter(executor, this);
+ mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
+ mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication();
// Register a callback to be notified about activities being created.
@@ -159,15 +161,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mActivityStartMonitor = new ActivityStartMonitor();
instrumentation.addMonitor(mActivityStartMonitor);
- mWindowLayoutComponent = windowLayoutComponent;
- mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
+ foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
}
private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> {
@Override
public void accept(List<CommonFoldingFeature> foldingFeatures) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
for (int i = 0; i < mTaskContainers.size(); i++) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
if (!taskContainer.isVisible()) {
@@ -184,9 +187,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
continue;
}
updateContainersInTask(wct, taskContainer);
- updateAnimationOverride(taskContainer);
}
- mPresenter.applyTransaction(wct);
+ // The WCT should be applied and merged to the device state change transition if
+ // there is one.
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -197,9 +201,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
synchronized (mLock) {
mSplitRules.clear();
mSplitRules.addAll(rules);
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- updateAnimationOverride(mTaskContainers.valueAt(i));
- }
}
}
@@ -224,6 +225,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@NonNull
+ @GuardedBy("mLock")
+ @VisibleForTesting
List<EmbeddingRule> getSplitRules() {
return mSplitRules;
}
@@ -240,13 +243,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
+ * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}.
+ */
+ @Override
+ public void clearSplitInfoCallback() {
+ synchronized (mLock) {
+ mEmbeddingCallback = null;
+ }
+ }
+
+ /**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
*/
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
+ transaction.getTransactionToken());
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
@@ -297,8 +312,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Notify the server, and the server should apply and merge the
// WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
- mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
- getTransitionType(wct), false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -323,6 +337,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
@@ -358,15 +373,22 @@ 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);
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);
+ 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.
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);
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
@@ -400,12 +422,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (container != null) {
// Cleanup if the TaskFragment vanished is not requested by the organizer.
removeContainer(container);
- // Make sure the top container is updated.
- final TaskFragmentContainer newTopContainer = getTopActiveContainer(
- container.getTaskId());
- if (newTopContainer != null) {
- updateContainer(wct, newTopContainer);
- }
+ // Make sure the containers in the Task are up-to-date.
+ updateContainersInTaskIfVisible(wct, container.getTaskId());
}
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
}
@@ -438,7 +456,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// parentInfo#isVisibleRequested is true.
return;
}
- onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
if (isInPictureInPicture(parentInfo.getConfiguration())) {
// No need to update presentation in PIP until the Task exit PIP.
return;
@@ -446,6 +463,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateContainersInTask(wct, taskContainer);
}
+ @GuardedBy("mLock")
+ void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) {
+ final TaskContainer taskContainer = getTaskContainer(taskId);
+ if (taskContainer != null && taskContainer.isVisible()) {
+ updateContainersInTask(wct, taskContainer);
+ }
+ }
+
+ @GuardedBy("mLock")
private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
@NonNull TaskContainer taskContainer) {
// Update all TaskFragments in the Task. Make a copy of the list since some may be
@@ -561,6 +587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
break;
@@ -581,55 +608,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
mTaskContainers.remove(taskContainer.getTaskId());
}
return;
}
}
- @GuardedBy("mLock")
- private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
- @NonNull Configuration config) {
- final boolean wasInPip = taskContainer.isInPictureInPicture();
- final boolean isInPIp = isInPictureInPicture(config);
-
- // We need to check the animation override when enter/exit PIP or has bounds changed.
- boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
- if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
- && !isInPIp) {
- // We don't care the bounds change when it has already entered PIP.
- shouldUpdateAnimationOverride = true;
- }
- if (shouldUpdateAnimationOverride) {
- updateAnimationOverride(taskContainer);
- }
- }
-
- /**
- * Updates if we should override transition animation. We only want to override if the Task
- * bounds is large enough for at least one split rule.
- */
- @GuardedBy("mLock")
- private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
- if (ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell with Shell transition enabled.
- return;
- }
- if (!taskContainer.isTaskBoundsInitialized()) {
- // We don't know about the Task bounds/windowingMode yet.
- return;
- }
-
- // We only want to override if the TaskContainer may show split.
- if (mayShowSplit(taskContainer)) {
- mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
- } else {
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
- }
- }
-
/** 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
@@ -771,6 +755,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Places the given activity to the top most TaskFragment in the task if there is any.
*/
+ @GuardedBy("mLock")
@VisibleForTesting
void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity activity) {
@@ -894,6 +879,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Finds the activity below the given activity. */
@VisibleForTesting
@Nullable
+ @GuardedBy("mLock")
Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity);
@@ -983,10 +969,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
@GuardedBy("mLock")
void onActivityDestroyed(@NonNull Activity activity) {
+ if (!activity.isFinishing()) {
+ // onDestroyed is triggered without finishing. This happens when the activity is
+ // relaunched. In this case, we don't want to cleanup the record.
+ return;
+ }
// Remove any pending appeared activity, as the server won't send finished activity to the
// organizer.
+ final IBinder activityToken = activity.getActivityToken();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).onActivityDestroyed(activity);
+ mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
}
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
@@ -999,11 +991,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- onTaskFragmentAppearEmptyTimeout(wct, container);
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
// Can be applied independently as a timeout callback.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- true /* shouldApplyIndependently */);
+ transactionRecord.apply(true /* shouldApplyIndependently */);
}
/**
@@ -1013,6 +1004,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
@@ -1150,6 +1142,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
taskId);
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
+ mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ TaskFragmentAnimationParams.DEFAULT);
return expandedContainer;
}
@@ -1188,16 +1182,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a container that this activity is registered with. An activity can only belong to one
* container, or no container at all.
*/
+ @GuardedBy("mLock")
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
- final IBinder activityToken = activity.getActivityToken();
+ return getContainerWithActivity(activity.getActivityToken());
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+ // Check pending appeared activity first because there can be a delay for the server
+ // update.
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (container.hasPendingAppearedActivity(activityToken)) {
+ return container;
+ }
+ }
+ }
+
+ // Check appeared activity if there is no such pending appeared activity.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- // Traverse from top to bottom in case an activity is added to top pending, and hasn't
- // received update from server yet.
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
- if (container.hasActivity(activityToken)) {
+ if (container.hasAppearedActivity(activityToken)) {
return container;
}
}
@@ -1205,6 +1216,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
+ @GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) {
return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
}
@@ -1213,14 +1225,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
/**
@@ -1232,10 +1244,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed.
* @param taskId parent Task of the new TaskFragment.
+ * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * set, the new container will be added right above it.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
- @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
+ @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1244,15 +1259,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this);
- if (!taskContainer.isTaskBoundsInitialized()) {
- // Get the initial bounds before the TaskFragment has appeared.
- final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask);
- if (!taskContainer.setTaskBounds(taskBounds)) {
- Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
- }
- }
- updateAnimationOverride(taskContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
return container;
}
@@ -1320,9 +1327,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
void removeContainer(@NonNull TaskFragmentContainer container) {
// Remove all split containers that included this one
final TaskContainer taskContainer = container.getTaskContainer();
- if (taskContainer == null) {
- return;
- }
taskContainer.mContainers.remove(container);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
@@ -1350,7 +1354,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Removes a secondary container for the given primary container if an existing split is
* already registered.
*/
- void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct,
+ // Suppress GuardedBy warning because lint asks to mark this method as
+ // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock
+ // itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer) {
// If the primary container was already in a split - remove the secondary container that
// is now covered by the new one that replaced it.
@@ -1368,6 +1377,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Returns the topmost not finished container in Task of given task id.
*/
+ @GuardedBy("mLock")
@Nullable
TaskFragmentContainer getTopActiveContainer(int taskId) {
final TaskContainer taskContainer = mTaskContainers.get(taskId);
@@ -1395,6 +1405,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ if (!container.getTaskContainer().isVisible()) {
+ // Wait until the Task is visible to avoid unnecessary update when the Task is still in
+ // background.
+ return;
+ }
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1510,14 +1525,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskFragmentContainer container = getContainerWithActivity(activity);
- // Don't launch placeholder if the container is occluded.
- if (container != null && container != getTopActiveContainer(container.getTaskId())) {
- return false;
- }
-
- final SplitContainer splitContainer = getActiveSplitForContainer(container);
- if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
- // Don't launch placeholder in primary split container
+ if (container != null && !allowLaunchPlaceholder(container)) {
+ // We don't allow activity in this TaskFragment to launch placeholder.
return false;
}
@@ -1545,6 +1554,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
+ /** Whether or not to allow activity in this container to launch placeholder. */
+ @GuardedBy("mLock")
+ private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
+ final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ if (container != topContainer) {
+ // The container is not the top most.
+ if (!container.isVisible()) {
+ // In case the container is visible (the one on top may be transparent), we may
+ // still want to launch placeholder even if it is not the top most.
+ return false;
+ }
+ if (topContainer.isWaitingActivityAppear()) {
+ // When the top container appeared info is not sent by the server yet, the visible
+ // check above may not be reliable.
+ return false;
+ }
+ }
+
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
+ // Don't launch placeholder for primary split container.
+ return false;
+ }
+ return true;
+ }
+
/**
* Gets the activity options for starting the placeholder activity. In case the placeholder is
* launched when the Task is in the background, we don't want to bring the Task to the front.
@@ -1552,6 +1587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
// Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1559,6 +1595,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check if the primary is resumed or if this is called when the primary is onCreated
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
+ // Only set trigger type if the launch happens in foreground.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1585,6 +1623,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
+
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1611,16 +1651,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Notifies listeners about changes to split states if necessary.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null) {
+ void updateCallbackIfNecessary() {
+ if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- if (!allActivitiesCreated()) {
- return;
- }
- List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1629,48 +1667,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
- * @return a list of descriptors for currently active split states. If the value returned is
- * null, that indicates that the active split states are in an intermediate state and should
- * not be reported.
+ * Returns a list of descriptors for currently active split states.
*/
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private List<SplitInfo> getActiveSplitStates() {
- List<SplitInfo> splitStates = new ArrayList<>();
+ final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
- .mSplitContainers;
- for (SplitContainer container : splitContainers) {
- if (container.getPrimaryContainer().isEmpty()
- || container.getSecondaryContainer().isEmpty()) {
- // We are in an intermediate state because either the split container is about
- // to be removed or the primary or secondary container are about to receive an
- // activity.
- return null;
- }
- final ActivityStack primaryContainer = container.getPrimaryContainer()
- .toActivityStack();
- final ActivityStack secondaryContainer = container.getSecondaryContainer()
- .toActivityStack();
- final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
- container.getSplitAttributes());
- splitStates.add(splitState);
- }
+ mTaskContainers.valueAt(i).getSplitStates(splitStates);
}
return splitStates;
}
/**
- * Checks if all activities that are registered with the containers have already appeared in
- * the client.
+ * Whether we can now report the split states to the client.
*/
- private boolean allActivitiesCreated() {
+ @GuardedBy("mLock")
+ private boolean readyToReportToClient() {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- for (TaskFragmentContainer container : containers) {
- if (!container.taskInfoActivityCountMatchesCreated()) {
- return false;
- }
+ if (mTaskContainers.valueAt(i).isInIntermediateState()) {
+ // If any Task is in an intermediate state, wait for the server update.
+ return false;
}
}
return true;
@@ -1730,6 +1747,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Nullable
+ @GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
@@ -1743,6 +1761,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Nullable
+ @GuardedBy("mLock")
TaskContainer getTaskContainer(int taskId) {
return mTaskContainers.get(taskId);
}
@@ -1751,6 +1770,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return mHandler;
}
+ @GuardedBy("mLock")
int getTaskId(@NonNull Activity activity) {
// Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an
// IPC call.
@@ -1843,6 +1863,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer)
*/
+ @GuardedBy("mLock")
boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer,
@NonNull Activity associatedActivity) {
final TaskFragmentContainer associatedContainer = getContainerWithActivity(
@@ -1859,6 +1880,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1890,33 +1916,51 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityCreated(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
+ activity);
// The WCT should be applied and merged to the activity launch transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityConfigurationChanged(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ SplitController.this.onActivityConfigurationChanged(
+ transactionRecord.getTransaction(), activity);
// The WCT should be applied and merged to the Task change transition so that the
// placeholder is launched in the same transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1940,6 +1984,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@VisibleForTesting
class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
@VisibleForTesting
+ @GuardedBy("mLock")
Intent mCurrentIntent;
@Override
@@ -1952,7 +1997,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
@@ -1967,7 +2016,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
@@ -1980,13 +2032,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (launchedInTaskFragment != null) {
// Make sure the WCT is applied immediately instead of being queued so that the
// TaskFragment will be ready before activity attachment.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
+ } else {
+ transactionRecord.abort();
}
}
@@ -1996,18 +2049,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
super.onStartActivityResult(result, bOptions);
- if (mCurrentIntent != null && result != START_SUCCESS) {
- // Clear the pending appeared intent if the activity was not started successfully.
- final IBinder token = bOptions.getBinder(
- ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
- if (token != null) {
- final TaskFragmentContainer container = getContainer(token);
- if (container != null) {
- container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
+ synchronized (mLock) {
+ if (mCurrentIntent != null && result != START_SUCCESS) {
+ // Clear the pending appeared intent if the activity was not started
+ // successfully.
+ final IBinder token = bOptions.getBinder(
+ ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+ if (token != null) {
+ final TaskFragmentContainer container = getContainer(token);
+ if (container != null) {
+ container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
+ }
}
}
+ mCurrentIntent = null;
}
- mCurrentIntent = null;
}
}
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 79603233ae14..14d244bbb6ce 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,9 +36,10 @@ import android.util.Size;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
+import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
-import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -50,6 +51,7 @@ import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttri
import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -62,7 +64,10 @@ import java.util.concurrent.Executor;
/**
* Controls the visual presentation of the splits according to the containers formed by
* {@link SplitController}.
+ *
+ * Note that all calls into this class must hold the {@link SplitController} internal lock.
*/
+@SuppressWarnings("GuardedBy")
class SplitPresenter extends JetpackTaskFragmentOrganizer {
@VisibleForTesting
static final int POSITION_START = 0;
@@ -133,12 +138,21 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
.setSplitType(new ExpandContainersSplitType())
.build();
+ private final WindowLayoutComponentImpl mWindowLayoutComponent;
private final SplitController mController;
- SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
+ SplitPresenter(@NonNull Executor executor,
+ @NonNull WindowLayoutComponentImpl windowLayoutComponent,
+ @NonNull SplitController controller) {
super(executor, controller);
+ mWindowLayoutComponent = windowLayoutComponent;
mController = controller;
registerOrganizer();
+ if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell when Shell transition is enabled.
+ overrideSplitAnimation();
+ }
}
/**
@@ -148,12 +162,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
void cleanupContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
container.finish(shouldFinishDependent, this, wct, mController);
-
- final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
- container.getTaskId());
- if (newTopContainer != null) {
- mController.updateContainer(wct, newTopContainer);
- }
+ // Make sure the containers in the Task is up-to-date.
+ mController.updateContainersInTaskIfVisible(wct, container.getTaskId());
}
/**
@@ -161,7 +171,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @return The newly created secondary container.
*/
@NonNull
- @GuardedBy("mController.mLock")
TaskFragmentContainer createNewSplitWithEmptySideContainer(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
@@ -173,7 +182,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
// Create new empty task fragment
final int taskId = primaryContainer.getTaskId();
@@ -186,6 +195,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRectBounds,
windowingMode);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -208,7 +218,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
*/
- @GuardedBy("mController.mLock")
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
@NonNull SplitPairRule rule) {
@@ -220,21 +229,21 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
containerToAvoid = curSecondaryContainer;
}
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
- secondaryActivity, secondaryRectBounds, containerToAvoid);
+ secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -251,7 +260,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
*/
private TaskFragmentContainer prepareContainerForActivity(
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
- @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+ @NonNull Rect bounds, @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) {
@@ -268,6 +278,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
.getWindowingModeForSplitTaskFragment(bounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
+ updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
return container;
}
@@ -283,7 +294,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder.
*/
- @GuardedBy("mController.mLock")
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
@@ -302,15 +312,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
- launchingActivity, taskId);
- final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(primaryRectBounds);
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(
+ null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ // Pass in the primary container to make sure it is added right above the primary.
+ primaryContainer);
+ final TaskContainer taskContainer = mController.getTaskContainer(taskId);
+ final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
- activityIntent, activityOptions, rule, windowingMode);
+ activityIntent, activityOptions, rule, windowingMode, splitAttributes);
if (isPlaceholder) {
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -323,7 +336,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @param updatedContainer The task fragment that was updated and caused this split update.
* @param wct WindowContainerTransaction that this update should be performed with.
*/
- @GuardedBy("mController.mLock")
void updateSplitContainer(@NonNull SplitContainer splitContainer,
@NonNull TaskFragmentContainer updatedContainer,
@NonNull WindowContainerTransaction wct) {
@@ -362,22 +374,26 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryRectBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
+ updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
}
- @GuardedBy("mController.mLock")
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
@NonNull SplitAttributes splitAttributes) {
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSplit(splitAttributes)) {
+ boolean isStacked = !shouldShowSplit(splitAttributes);
+ if (isStacked) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
+ setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
}
/**
@@ -385,7 +401,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* creation has not been reported from the server yet.
*/
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
- void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@Nullable Rect bounds) {
if (container.getInfo() == null) {
@@ -404,17 +420,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Override
- void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ final TaskFragmentContainer container = mController.getContainer(
+ fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
"Creating a task fragment that is not registered with controller.");
}
- container.setLastRequestedBounds(bounds);
- container.setLastRequestedWindowingMode(windowingMode);
- super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
+ container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
+ super.createTaskFragment(wct, fragmentOptions);
}
@Override
@@ -453,6 +470,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateWindowingMode(wct, fragmentToken, windowingMode);
}
+ @Override
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @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"
+ + " not registered with controller.");
+ }
+
+ if (container.areLastRequestedAnimationParamsEqual(animationParams)) {
+ // Return early if the animation params were already requested
+ return;
+ }
+
+ container.setLastRequestAnimationParams(animationParams);
+ super.updateAnimationParams(wct, fragmentToken, animationParams);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
@@ -489,8 +524,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|| splitContainer.getSecondaryContainer().getInfo() == null) {
return RESULT_EXPAND_FAILED_NO_TF_INFO;
}
- expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
- expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+ final IBinder primaryToken =
+ splitContainer.getPrimaryContainer().getTaskFragmentToken();
+ final IBinder secondaryToken =
+ splitContainer.getSecondaryContainer().getTaskFragmentToken();
+ expandTaskFragment(wct, primaryToken);
+ expandTaskFragment(wct, secondaryToken);
+ // Set the companion TaskFragment when the two containers stacked.
+ setCompanionTaskFragment(wct, primaryToken, secondaryToken,
+ splitContainer.getSplitRule(), true /* isStacked */);
return RESULT_EXPANDED;
}
return RESULT_NOT_EXPANDED;
@@ -504,7 +546,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
}
- @GuardedBy("mController.mLock")
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
@NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
@@ -520,7 +561,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
minDimensionsPair);
}
- final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent
+ final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
.getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
taskConfiguration.windowConfiguration);
final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
@@ -556,8 +597,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- static Pair<Size, Size> getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
+ private static Pair<Size, Size> getActivitiesMinDimensionsPair(
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@@ -603,7 +644,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new Size(windowLayout.minWidth, windowLayout.minHeight);
}
- static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
@Nullable Size minDimensions) {
if (minDimensions == null) {
return false;
@@ -802,7 +843,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final int displayId = taskProperties.getDisplayId();
final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
.windowConfiguration;
- final WindowLayoutInfo info = mController.mWindowLayoutComponent
+ final WindowLayoutInfo info = mWindowLayoutComponent
.getCurrentWindowLayoutInfo(displayId, windowConfiguration);
final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
if (displayFeatures.isEmpty()) {
@@ -917,11 +958,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (taskContainer != null) {
return taskContainer.getTaskProperties();
}
- // Use a copy of configuration because activity's configuration may be updated later,
- // or we may get unexpected TaskContainer's configuration if Activity's configuration is
- // updated. An example is Activity is going to be in split.
- return new TaskProperties(activity.getDisplayId(),
- new Configuration(activity.getResources().getConfiguration()));
+ return TaskProperties.getTaskPropertiesFromActivity(activity);
}
@NonNull
@@ -935,16 +972,4 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// TODO(b/190433398): Supply correct insets.
return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
}
-
- /** Obtains the bounds from a non-embedded Activity. */
- @NonNull
- static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
- final WindowConfiguration windowConfiguration =
- activity.getResources().getConfiguration().windowConfiguration;
- if (!activity.isInMultiWindowMode()) {
- // In fullscreen mode the max bounds should correspond to the task bounds.
- return windowConfiguration.getMaxBounds();
- }
- return windowConfiguration.getBounds();
- }
}
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 91573ffef568..03f4dc9c1167 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -20,14 +20,17 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
import android.app.Activity;
+import android.app.ActivityClient;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.Log;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -41,14 +44,11 @@ import java.util.Set;
/** Represents TaskFragments and split pairs below a Task. */
class TaskContainer {
+ private static final String TAG = TaskContainer.class.getSimpleName();
/** The unique task id. */
private final int mTaskId;
- // TODO(b/240219484): consolidate to mConfiguration
- /** Available window bounds of this Task. */
- private final Rect mTaskBounds = new Rect();
-
/** Active TaskFragments in this Task. */
@NonNull
final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -86,10 +86,10 @@ class TaskContainer {
throw new IllegalArgumentException("Invalid Task id");
}
mTaskId = taskId;
- // Make a copy in case the activity's config is updated, and updates the TaskContainer's
- // config unexpectedly.
- mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
- mDisplayId = activityInTask.getDisplayId();
+ final TaskProperties taskProperties = TaskProperties
+ .getTaskPropertiesFromActivity(activityInTask);
+ mConfiguration = taskProperties.getConfiguration();
+ mDisplayId = taskProperties.getDisplayId();
// Note that it is always called when there's a new Activity is started, which implies
// the host task is visible.
mIsVisible = true;
@@ -108,25 +108,6 @@ class TaskContainer {
}
@NonNull
- Rect getTaskBounds() {
- return mTaskBounds;
- }
-
- /** Returns {@code true} if the bounds is changed. */
- boolean setTaskBounds(@NonNull Rect taskBounds) {
- if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) {
- mTaskBounds.set(taskBounds);
- return true;
- }
- return false;
- }
-
- /** Whether the Task bounds has been initialized. */
- boolean isTaskBoundsInitialized() {
- return !mTaskBounds.isEmpty();
- }
-
- @NonNull
Configuration getConfiguration() {
// Make a copy in case the config is updated unexpectedly.
return new Configuration(mConfiguration);
@@ -140,7 +121,7 @@ class TaskContainer {
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
- mIsVisible = info.isVisibleRequested();
+ mIsVisible = info.isVisible();
}
/**
@@ -185,16 +166,16 @@ class TaskContainer {
}
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull Activity activity) {
+ void onActivityDestroyed(@NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.onActivityDestroyed(activity);
+ container.onActivityDestroyed(activityToken);
}
}
/** Removes the pending appeared activity from all TaskFragments in this Task. */
- void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.removePendingAppearedActivity(pendingAppearedActivity);
+ container.removePendingAppearedActivity(activityToken);
}
}
@@ -221,6 +202,24 @@ class TaskContainer {
return mContainers.indexOf(child);
}
+ /** Whether the Task is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.isInIntermediateState()) {
+ // We are in an intermediate state to wait for server update on this TaskFragment.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
+ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+ for (SplitContainer container : mSplitContainers) {
+ outSplitStates.add(container.toSplitInfo());
+ }
+ }
+
/**
* A wrapper class which contains the display ID and {@link Configuration} of a
* {@link TaskContainer}
@@ -243,5 +242,45 @@ class TaskContainer {
Configuration getConfiguration() {
return mConfiguration;
}
+
+ /**
+ * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is
+ * associated with.
+ * <p>
+ * Note that for most case, caller should use
+ * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before
+ * the {@code activity} goes into split.
+ * </p><p>
+ * If the {@link Activity} is in fullscreen, override
+ * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()}
+ * in case the {@link Activity} is letterboxed. Otherwise, get the Task
+ * {@link Configuration} from the server side or use {@link Activity}'s
+ * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained.
+ */
+ @NonNull
+ static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) {
+ final int displayId = activity.getDisplayId();
+ // Use a copy of configuration because activity's configuration may be updated later,
+ // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+ // updated. An example is Activity is going to be in split.
+ final Configuration activityConfig = new Configuration(
+ activity.getResources().getConfiguration());
+ final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration;
+ final int windowingMode = windowConfiguration.getWindowingMode();
+ if (!inMultiWindowMode(windowingMode)) {
+ // Use the max bounds in fullscreen in case the Activity is letterboxed.
+ windowConfiguration.setBounds(windowConfiguration.getMaxBounds());
+ return new TaskProperties(displayId, activityConfig);
+ }
+ final Configuration taskConfig = ActivityClient.getInstance()
+ .getTaskConfiguration(activity.getActivityToken());
+ if (taskConfig == null) {
+ Log.w(TAG, "Could not obtain task configuration for activity:" + activity);
+ // Still report activity config if task config cannot be obtained from the server
+ // side.
+ return new TaskProperties(displayId, activityConfig);
+ }
+ return new TaskProperties(displayId, taskConfig);
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index af5d8c561874..33220c44a3b5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -18,7 +18,9 @@ package androidx.window.extensions.embedding;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import android.graphics.Point;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.RemoteAnimationTarget;
@@ -49,6 +51,16 @@ class TaskFragmentAnimationAdapter {
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
private final Rect mWholeAnimationBounds = new Rect();
+ /**
+ * Area in absolute coordinate that should represent all the content to show for this window.
+ * This should be the end bounds for opening window, and start bounds for closing window in case
+ * the window is resizing during the open/close transition.
+ */
+ @NonNull
+ private final Rect mContentBounds = new Rect();
+ /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+ @NonNull
+ private final Point mContentRelOffset = new Point();
@NonNull
final Transformation mTransformation = new Transformation();
@@ -78,6 +90,21 @@ class TaskFragmentAnimationAdapter {
mTarget = target;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
+ if (target.mode == MODE_CLOSING) {
+ // 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.
+ final Rect startBounds = target.startBounds;
+ final Rect endBounds = target.screenSpaceBounds;
+ mContentBounds.set(startBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ } else {
+ mContentBounds.set(target.screenSpaceBounds);
+ mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+ }
}
/**
@@ -108,8 +135,7 @@ class TaskFragmentAnimationAdapter {
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- mTransformation.getMatrix().postTranslate(
- mTarget.localBounds.left, mTarget.localBounds.top);
+ mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -117,9 +143,8 @@ class TaskFragmentAnimationAdapter {
// positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mTarget.screenSpaceBounds);
- final Rect localBounds = mTarget.localBounds;
- cropRect.offset(positionX - localBounds.left, positionY - localBounds.top);
+ final Rect cropRect = new Rect(mContentBounds);
+ cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
// Store the current offset of the surface top left from (0,0) in absolute coordinate.
final int offsetX = cropRect.left;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139bb0b2..d7eb9a01f57c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@ package androidx.window.extensions.embedding;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import android.util.ArraySet;
import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@ class TaskFragmentAnimationController {
private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
@VisibleForTesting
final RemoteAnimationDefinition mDefinition;
- /** Task Ids that we have registered for remote animation. */
- private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+ private boolean mIsRegistered;
TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
@@ -54,39 +50,30 @@ class TaskFragmentAnimationController {
new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
}
- void registerRemoteAnimations(int taskId) {
+ void registerRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "registerRemoteAnimations");
}
- if (mRegisterTasks.contains(taskId)) {
+ if (mIsRegistered) {
return;
}
- mOrganizer.registerRemoteAnimations(taskId, mDefinition);
- mRegisterTasks.add(taskId);
+ mOrganizer.registerRemoteAnimations(mDefinition);
+ mIsRegistered = true;
}
- void unregisterRemoteAnimations(int taskId) {
+ void unregisterRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "unregisterRemoteAnimations");
}
- if (!mRegisterTasks.contains(taskId)) {
+ if (!mIsRegistered) {
return;
}
- mOrganizer.unregisterRemoteAnimations(taskId);
- mRegisterTasks.remove(taskId);
- }
-
- void unregisterAllRemoteAnimations() {
- final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
- for (int taskId : tasks) {
- unregisterRemoteAnimations(taskId);
- }
+ mOrganizer.unregisterRemoteAnimations();
+ mIsRegistered = false;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e881059..dcc12ac07589 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -17,14 +17,14 @@
package androidx.window.extensions.embedding;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+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.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import android.animation.Animator;
@@ -169,11 +169,9 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case TRANSIT_OLD_TASK_OPEN:
return createOpenAnimationAdapters(targets);
case TRANSIT_OLD_ACTIVITY_CLOSE:
case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- case TRANSIT_OLD_TASK_CLOSE:
return createCloseAnimationAdapters(targets);
case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
return createChangeAnimationAdapters(targets);
@@ -256,9 +254,13 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
@NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
+ if (shouldUseJumpCutForChangeAnimation(targets)) {
+ return new ArrayList<>();
+ }
+
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
for (RemoteAnimationTarget target : targets) {
- if (target.startBounds != null) {
+ if (target.mode == MODE_CHANGING) {
// This is the target with bounds change.
final Animation[] animations =
mAnimationSpec.createChangeBoundsChangeAnimations(target);
@@ -285,4 +287,24 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
return adapters;
}
+
+ /**
+ * Whether we should use jump cut for the change transition.
+ * This normally happens when opening a new secondary with the existing primary using a
+ * different split layout. This can be complicated, like from horizontal to vertical split with
+ * new split pairs.
+ * Uses a jump cut animation to simplify.
+ */
+ private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
+ boolean hasOpeningWindow = false;
+ boolean hasClosingWindow = false;
+ for (RemoteAnimationTarget target : targets) {
+ if (target.hasAnimatingParent) {
+ continue;
+ }
+ hasOpeningWindow |= target.mode == MODE_OPENING;
+ hasClosingWindow |= target.mode == MODE_CLOSING;
+ }
+ return hasOpeningWindow && hasClosingWindow;
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index ef5ea563de12..1f866c3b99c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -86,13 +86,23 @@ class TaskFragmentAnimationSpec {
/** Animation for target that is opening in a change transition. */
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated in from left or right depends on its position.
- final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect bounds = target.screenSpaceBounds;
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -103,13 +113,24 @@ class TaskFragmentAnimationSpec {
/** Animation for target that is closing in a change transition. */
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated out to left or right depends on its position.
- final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ // Use startBounds if the window is closing in case it may also resize.
+ final Rect bounds = target.startBounds;
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -161,7 +182,7 @@ class TaskFragmentAnimationSpec {
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
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 18712aed1be6..076856c373d6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -43,6 +44,9 @@ import java.util.List;
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
* on the server side.
*/
+// Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
+// SplitController.mTaskContainers which is guarded.
+@SuppressWarnings("GuardedBy")
class TaskFragmentContainer {
private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
@@ -66,11 +70,11 @@ class TaskFragmentContainer {
TaskFragmentInfo mInfo;
/**
- * Activities that are being reparented or being started to this container, but haven't been
- * added to {@link #mInfo} yet.
+ * Activity tokens that are being reparented or being started to this container, but haven't
+ * been added to {@link #mInfo} yet.
*/
@VisibleForTesting
- final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
+ final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();
/**
* When this container is created for an {@link Intent} to start within, we store that Intent
@@ -84,8 +88,11 @@ class TaskFragmentContainer {
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
new ArrayList<>();
- /** Individual associated activities in different containers that should be finished on exit. */
- private final List<Activity> mActivitiesToFinishOnExit = new ArrayList<>();
+ /**
+ * Individual associated activity tokens in different containers that should be finished on
+ * exit.
+ */
+ private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -102,6 +109,13 @@ class TaskFragmentContainer {
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
/**
+ * TaskFragmentAnimationParams that was requested last via
+ * {@link android.window.WindowContainerTransaction}.
+ */
+ @NonNull
+ private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -112,10 +126,12 @@ class TaskFragmentContainer {
/**
* 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
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
- @NonNull SplitController controller) {
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -124,7 +140,16 @@ class TaskFragmentContainer {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
- taskContainer.mContainers.add(this);
+ if (pairedPrimaryContainer != null) {
+ if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
+ throw new IllegalArgumentException(
+ "pairedPrimaryContainer must be in the same Task");
+ }
+ final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
+ taskContainer.mContainers.add(primaryIndex + 1, this);
+ } else {
+ taskContainer.mContainers.add(this);
+ }
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
}
@@ -158,24 +183,48 @@ class TaskFragmentContainer {
// in this intermediate state.
// Place those on top of the list since they will be on the top after reported from the
// server.
- for (Activity activity : mPendingAppearedActivities) {
- if (!activity.isFinishing()) {
+ for (IBinder token : mPendingAppearedActivities) {
+ final Activity activity = mController.getActivity(token);
+ if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
}
}
return allActivities;
}
- /**
- * Checks if the count of activities from the same process in task fragment info corresponds to
- * the ones created and available on the client side.
- */
- boolean taskInfoActivityCountMatchesCreated() {
+ /** Whether this TaskFragment is visible. */
+ boolean isVisible() {
+ return mInfo != null && mInfo.isVisible();
+ }
+
+ /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
if (mInfo == null) {
- return false;
+ // Haven't received onTaskFragmentAppeared event.
+ return true;
+ }
+ if (mInfo.isEmpty()) {
+ // Empty TaskFragment will be removed or will have activity launched into it soon.
+ return true;
+ }
+ if (!mPendingAppearedActivities.isEmpty()) {
+ // Reparented activity hasn't appeared.
+ return true;
}
- return mPendingAppearedActivities.isEmpty()
- && mInfo.getActivities().size() == collectNonFinishingActivities().size();
+ // Check if there is any reported activity that is no longer alive.
+ for (IBinder token : mInfo.getActivities()) {
+ final Activity activity = mController.getActivity(token);
+ if (activity == null && !mTaskContainer.isVisible()) {
+ // Activity can be null if the activity is not attached to process yet. That can
+ // happen when the activity is started in background.
+ continue;
+ }
+ if (activity == null || activity.isFinishing()) {
+ // One of the reported activity is no longer alive, wait for the server update.
+ return true;
+ }
+ }
+ return false;
}
@NonNull
@@ -185,55 +234,59 @@ class TaskFragmentContainer {
/** Adds the activity that will be reparented to this container. */
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
- if (hasActivity(pendingAppearedActivity.getActivityToken())) {
+ final IBinder activityToken = pendingAppearedActivity.getActivityToken();
+ if (hasActivity(activityToken)) {
return;
}
- // Remove the pending activity from other TaskFragments.
- mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
- mPendingAppearedActivities.add(pendingAppearedActivity);
- updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
+ // Remove the pending activity from other TaskFragments in case the activity is reparented
+ // again before the server update.
+ mTaskContainer.cleanupPendingAppearedActivity(activityToken);
+ mPendingAppearedActivities.add(activityToken);
+ updateActivityClientRecordTaskFragmentToken(activityToken);
}
/**
* Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
* activity. This makes sure the token is up-to-date if the activity is relaunched later.
*/
- private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
+ private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
final ActivityThread.ActivityClientRecord record = ActivityThread
- .currentActivityThread().getActivityClient(activity.getActivityToken());
+ .currentActivityThread().getActivityClient(activityToken);
if (record != null) {
record.mTaskFragmentToken = mToken;
}
}
- void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
- mPendingAppearedActivities.remove(pendingAppearedActivity);
+ void removePendingAppearedActivity(@NonNull IBinder activityToken) {
+ mPendingAppearedActivities.remove(activityToken);
}
+ @GuardedBy("mController.mLock")
void clearPendingAppearedActivities() {
- final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
+ final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
// Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
// current TaskFragment.
mPendingAppearedActivities.clear();
mPendingAppearedIntent = null;
// For removed pending activities, we need to update the them to their previous containers.
- for (Activity activity : cleanupActivities) {
+ for (IBinder activityToken : cleanupActivities) {
final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
- activity);
+ activityToken);
if (curContainer != null) {
- curContainer.updateActivityClientRecordTaskFragmentToken(activity);
+ curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
}
}
}
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull Activity activity) {
- removePendingAppearedActivity(activity);
+ void onActivityDestroyed(@NonNull IBinder activityToken) {
+ removePendingAppearedActivity(activityToken);
if (mInfo != null) {
// Remove the activity now because there can be a delay before the server callback.
- mInfo.getActivities().remove(activity.getActivityToken());
+ mInfo.getActivities().remove(activityToken);
}
+ mActivitiesToFinishOnExit.remove(activityToken);
}
@Nullable
@@ -257,16 +310,24 @@ class TaskFragmentContainer {
mPendingAppearedIntent = null;
}
- boolean hasActivity(@NonNull IBinder token) {
- if (mInfo != null && mInfo.getActivities().contains(token)) {
- return true;
- }
- for (Activity activity : mPendingAppearedActivities) {
- if (activity.getActivityToken().equals(token)) {
- return true;
- }
- }
- return false;
+ boolean hasActivity(@NonNull IBinder activityToken) {
+ // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
+ // sure the controller considers this container as the one containing the activity.
+ // This is needed when the activity is added as pending appeared activity to one
+ // TaskFragment while it is also an appeared activity in another.
+ return mController.getContainerWithActivity(activityToken) == this;
+ }
+
+ /** Whether this activity has appeared in the TaskFragment on the server side. */
+ boolean hasAppearedActivity(@NonNull IBinder activityToken) {
+ return mInfo != null && mInfo.getActivities().contains(activityToken);
+ }
+
+ /**
+ * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
+ */
+ boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
+ return mPendingAppearedActivities.contains(activityToken);
}
int getRunningActivityCount() {
@@ -324,8 +385,8 @@ class TaskFragmentContainer {
// Cleanup activities that were being re-parented
List<IBinder> infoActivities = mInfo.getActivities();
for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
- final Activity activity = mPendingAppearedActivities.get(i);
- if (infoActivities.contains(activity.getActivityToken())) {
+ final IBinder activityToken = mPendingAppearedActivities.get(i);
+ if (infoActivities.contains(activityToken)) {
mPendingAppearedActivities.remove(i);
}
}
@@ -374,7 +435,7 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.add(activityToFinish);
+ mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
}
/**
@@ -384,7 +445,7 @@ class TaskFragmentContainer {
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.remove(activityToRemove);
+ mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
}
/** Removes all dependencies that should be finished when this container is finished. */
@@ -400,6 +461,7 @@ class TaskFragmentContainer {
* Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together.
*/
+ @GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
if (!mIsFinished) {
@@ -424,6 +486,7 @@ class TaskFragmentContainer {
mInfo = null;
}
+ @GuardedBy("mController.mLock")
private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
// Finish own activities
@@ -452,8 +515,9 @@ class TaskFragmentContainer {
mContainersToFinishOnExit.clear();
// Finish associated activities
- for (Activity activity : mActivitiesToFinishOnExit) {
- if (activity.isFinishing()
+ for (IBinder activityToken : mActivitiesToFinishOnExit) {
+ final Activity activity = mController.getActivity(activityToken);
+ if (activity == null || activity.isFinishing()
|| controller.shouldRetainAssociatedActivity(this, activity)) {
continue;
}
@@ -504,6 +568,21 @@ class TaskFragmentContainer {
mLastRequestedWindowingMode = windowingModes;
}
+ /**
+ * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ */
+ boolean areLastRequestedAnimationParamsEqual(
+ @NonNull TaskFragmentAnimationParams animationParams) {
+ return mLastAnimationParams.equals(animationParams);
+ }
+
+ /**
+ * Updates the last requested {@link TaskFragmentAnimationParams}.
+ */
+ void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
+ mLastAnimationParams = animationParams;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
@@ -522,7 +601,8 @@ class TaskFragmentContainer {
}
int maxMinWidth = mInfo.getMinimumWidth();
int maxMinHeight = mInfo.getMinimumHeight();
- for (Activity activity : mPendingAppearedActivities) {
+ for (IBinder activityToken : mPendingAppearedActivities) {
+ final Activity activity = mController.getActivity(activityToken);
final Size minDimensions = SplitPresenter.getMinDimensions(activity);
if (minDimensions == null) {
continue;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
new file mode 100644
index 000000000000..0071fea41aa8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
+
+import android.os.IBinder;
+import android.view.WindowManager.TransitionType;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
+ * state changes and app interactions.
+ *
+ * A typical use flow:
+ * 1. Call {@link #startNewTransaction} to start tracking the changes.
+ * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
+ * will start a new transition on system server.
+ * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
+ * changes.
+ * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
+ * the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
+ * dispose the current one.
+ *
+ * Note:
+ * There should be only one transaction at a time. The caller should not call
+ * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} to the previous transaction.
+ */
+class TransactionManager {
+
+ @NonNull
+ private final TaskFragmentOrganizer mOrganizer;
+
+ @Nullable
+ private TransactionRecord mCurrentTransaction;
+
+ TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ }
+
+ @NonNull
+ TransactionRecord startNewTransaction() {
+ return startNewTransaction(null /* taskFragmentTransactionToken */);
+ }
+
+ /**
+ * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
+ * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
+ * transaction until {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} is called.
+ * @param taskFragmentTransactionToken {@link android.window.TaskFragmentTransaction
+ * #getTransactionToken()} if this is a response to a
+ * {@link android.window.TaskFragmentTransaction}.
+ */
+ @NonNull
+ TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
+ if (mCurrentTransaction != null) {
+ mCurrentTransaction = null;
+ throw new IllegalStateException(
+ "The previous transaction has not been applied or aborted,");
+ }
+ mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
+ return mCurrentTransaction;
+ }
+
+ /**
+ * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
+ */
+ @NonNull
+ TransactionRecord getCurrentTransactionRecord() {
+ if (mCurrentTransaction == null) {
+ throw new IllegalStateException("startNewTransaction() is not invoked before calling"
+ + " getCurrentTransactionRecord().");
+ }
+ return mCurrentTransaction;
+ }
+
+ /** The current transaction. The manager should only handle one transaction at a time. */
+ class TransactionRecord {
+ /**
+ * {@link WindowContainerTransaction} containing the current change.
+ * @see #startNewTransaction(IBinder)
+ * @see #apply (boolean)
+ */
+ @NonNull
+ private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
+
+ /**
+ * If the current transaction is a response to a
+ * {@link android.window.TaskFragmentTransaction}, this is the
+ * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
+ * @see #startNewTransaction(IBinder)
+ */
+ @Nullable
+ private final IBinder mTaskFragmentTransactionToken;
+
+ /**
+ * To track of the origin type of the current {@link #mTransaction}. When
+ * {@link #apply (boolean)} to start a new transition, this is the type to request.
+ * @see #setOriginType(int)
+ * @see #getTransactionTransitionType()
+ */
+ @TransitionType
+ private int mOriginType = TRANSIT_NONE;
+
+ TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
+ mTaskFragmentTransactionToken = taskFragmentTransactionToken;
+ }
+
+ @NonNull
+ WindowContainerTransaction getTransaction() {
+ ensureCurrentTransaction();
+ return mTransaction;
+ }
+
+ /**
+ * Sets the {@link TransitionType} 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) {
+ ensureCurrentTransaction();
+ if (mOriginType != TRANSIT_NONE) {
+ // Skip if the origin type has already been set.
+ return;
+ }
+ mOriginType = type;
+ }
+
+ /**
+ * Requests the system server to apply the current transaction started from
+ * {@link #startNewTransaction}.
+ * @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will
+ * request a new transition, which will be queued until the
+ * sync engine is free if there is any other active sync.
+ * If {@code false}, the {@link #startNewTransaction} will
+ * be directly applied to the active sync.
+ */
+ void apply(boolean shouldApplyIndependently) {
+ ensureCurrentTransaction();
+ if (mTaskFragmentTransactionToken != null) {
+ // If this is a response to a TaskFragmentTransaction.
+ mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
+ getTransactionTransitionType(), shouldApplyIndependently);
+ } else {
+ mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
+ shouldApplyIndependently);
+ }
+ dispose();
+ }
+
+ /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
+ void abort() {
+ ensureCurrentTransaction();
+ dispose();
+ }
+
+ private void dispose() {
+ TransactionManager.this.mCurrentTransaction = null;
+ }
+
+ private void ensureCurrentTransaction() {
+ if (TransactionManager.this.mCurrentTransaction != this) {
+ throw new IllegalStateException(
+ "This transaction has already been apply() or abort().");
+ }
+ }
+
+ /**
+ * Gets the {@link TransitionType} that we will request transition with for the
+ * current {@link WindowContainerTransaction}.
+ */
+ @VisibleForTesting
+ @TransitionType
+ int getTransactionTransitionType() {
+ // Use TRANSIT_CHANGE as default if there is not opening/closing window.
+ return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c76f568e117f..c9f870005eb9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -35,21 +35,23 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.window.WindowContext;
+import android.view.WindowManager;
+import android.window.TaskFragmentOrganizer;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.util.DataProducer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -64,28 +66,39 @@ import java.util.function.Consumer;
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
new ArrayMap<>();
+ @GuardedBy("mLock")
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
+ @GuardedBy("mLock")
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
- private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ @GuardedBy("mLock")
+ private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>();
- public WindowLayoutComponentImpl(@NonNull Context context) {
+ private final TaskFragmentOrganizer mTaskFragmentOrganizer;
+
+ public WindowLayoutComponentImpl(@NonNull Context context,
+ @NonNull TaskFragmentOrganizer taskFragmentOrganizer,
+ @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
- RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
- mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
- foldingFeatureProducer);
+ mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+ mTaskFragmentOrganizer = taskFragmentOrganizer;
}
/** Registers to listen to {@link CommonFoldingFeature} changes */
public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
- mFoldingFeatureProducer.addDataChangedCallback(consumer);
+ synchronized (mLock) {
+ mFoldingFeatureProducer.addDataChangedCallback(consumer);
+ }
}
/**
@@ -103,32 +116,43 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
* as a parameter.
+ *
+ * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
+ * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
+ * together. However only the first registered consumer of a {@link Context} will actually
+ * invoke {@link #addWindowLayoutInfoListener(Context, Consumer)}.
+ * Here we enforce that {@link #addWindowLayoutInfoListener(Context, Consumer)} can only be
+ * called once for each {@link Context}.
*/
- // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+ @Override
public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
@NonNull Consumer<WindowLayoutInfo> consumer) {
- if (mWindowLayoutChangeListeners.containsKey(context)
- || mWindowLayoutChangeListeners.containsValue(consumer)) {
- // Early return if the listener or consumer has been registered.
- return;
- }
- if (!context.isUiContext()) {
- throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity or a WindowContext");
- }
- mFoldingFeatureProducer.getData((features) -> {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
- WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
- consumer.accept(newWindowLayout);
- });
- mWindowLayoutChangeListeners.put(context, consumer);
-
- if (context instanceof WindowContext) {
+ synchronized (mLock) {
+ if (mWindowLayoutChangeListeners.containsKey(context)
+ // In theory this method can be called on the same consumer with different
+ // context.
+ || mWindowLayoutChangeListeners.containsValue(consumer)) {
+ return;
+ }
+ if (!context.isUiContext()) {
+ throw new IllegalArgumentException("Context must be a UI Context, which should be"
+ + " an Activity, WindowContext or InputMethodService");
+ }
+ mFoldingFeatureProducer.getData((features) -> {
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
+ consumer.accept(newWindowLayout);
+ });
+ mWindowLayoutChangeListeners.put(context, consumer);
+
final IBinder windowContextToken = context.getWindowContextToken();
- final WindowContextConfigListener listener =
- new WindowContextConfigListener(windowContextToken);
- context.registerComponentCallbacks(listener);
- mWindowContextConfigListeners.put(windowContextToken, listener);
+ if (windowContextToken != null) {
+ // We register component callbacks for window contexts. For activity contexts, they
+ // will receive callbacks from NotifyOnConfigurationChanged instead.
+ final ConfigurationChangeListener listener =
+ new ConfigurationChangeListener(windowContextToken);
+ context.registerComponentCallbacks(listener);
+ mConfigurationChangeListeners.put(windowContextToken, listener);
+ }
}
}
@@ -139,27 +163,31 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*/
@Override
public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
- for (Context context : mWindowLayoutChangeListeners.keySet()) {
- if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
- continue;
- }
- if (context instanceof WindowContext) {
+ synchronized (mLock) {
+ for (Context context : mWindowLayoutChangeListeners.keySet()) {
+ if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
+ continue;
+ }
final IBinder token = context.getWindowContextToken();
- context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
- mWindowContextConfigListeners.remove(token);
+ if (token != null) {
+ context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+ mConfigurationChangeListeners.remove(token);
+ }
+ break;
}
- break;
+ mWindowLayoutChangeListeners.values().remove(consumer);
}
- mWindowLayoutChangeListeners.values().remove(consumer);
}
+ @GuardedBy("mLock")
@NonNull
- Set<Context> getContextsListeningForLayoutChanges() {
+ private Set<Context> getContextsListeningForLayoutChanges() {
return mWindowLayoutChangeListeners.keySet();
}
+ @GuardedBy("mLock")
private boolean isListeningForLayoutChanges(IBinder token) {
- for (Context context: getContextsListeningForLayoutChanges()) {
+ for (Context context : getContextsListeningForLayoutChanges()) {
if (token.equals(Context.getToken(context))) {
return true;
}
@@ -167,10 +195,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return false;
}
- protected boolean hasListeners() {
- return !mWindowLayoutChangeListeners.isEmpty();
- }
-
/**
* A convenience method to translate from the common feature state to the extensions feature
* state. More specifically, translates from {@link CommonFoldingFeature.State} to
@@ -194,21 +218,26 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
- mLastReportedFoldingFeatures.clear();
- mLastReportedFoldingFeatures.addAll(storedFeatures);
- for (Context context : getContextsListeningForLayoutChanges()) {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
- Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
- WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
- layoutConsumer.accept(newWindowLayout);
+ synchronized (mLock) {
+ mLastReportedFoldingFeatures.clear();
+ mLastReportedFoldingFeatures.addAll(storedFeatures);
+ for (Context context : getContextsListeningForLayoutChanges()) {
+ // Get the WindowLayoutInfo from the activity and pass the value to the
+ // layoutConsumer.
+ Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(
+ context);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
+ layoutConsumer.accept(newWindowLayout);
+ }
}
}
/**
* Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
* valid state is found.
+ *
* @param context a proxy for the {@link android.view.Window} that contains the
- * {@link DisplayFeature}.
+ * {@link DisplayFeature}.
*/
private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context,
List<CommonFoldingFeature> storedFeatures) {
@@ -220,15 +249,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}.
*
* @return current {@link WindowLayoutInfo} on the default display. Returns
- * empty {@link WindowLayoutInfo} on secondary displays.
+ * empty {@link WindowLayoutInfo} on secondary displays.
*/
@NonNull
public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId,
@NonNull WindowConfiguration windowConfiguration) {
- return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures);
+ synchronized (mLock) {
+ return getWindowLayoutInfo(displayId, windowConfiguration,
+ mLastReportedFoldingFeatures);
+ }
}
- /** @see #getWindowLayoutInfo(Context, List) */
+ /** @see #getWindowLayoutInfo(Context, List) */
private WindowLayoutInfo getWindowLayoutInfo(int displayId,
@NonNull WindowConfiguration windowConfiguration,
List<CommonFoldingFeature> storedFeatures) {
@@ -252,7 +284,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param context a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
- * are within the {@link android.view.Window} of the {@link Activity}
+ * @return a {@link List} of {@link DisplayFeature}s that are within the
+ * {@link android.view.Window} of the {@link Activity}
*/
private List<DisplayFeature> getDisplayFeatures(
@NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
@@ -291,8 +324,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
/**
- * Checks whether display features should be reported for the activity.
+ * Calculates if the display features should be reported for the UI Context. The calculation
+ * uses the task information because that is accurate for Activities in ActivityEmbedding mode.
* TODO(b/238948678): Support reporting display features in all windowing modes.
+ *
+ * @return true if the display features should be reported for the UI Context, false otherwise.
*/
private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
int displayId = context.getDisplay().getDisplayId();
@@ -301,26 +337,49 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return false;
}
final int windowingMode;
- if (context instanceof Activity) {
- windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
- context.getActivityToken());
+ IBinder activityToken = context.getActivityToken();
+ if (activityToken != null) {
+ final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
+ activityToken);
+ if (taskConfig == null) {
+ // If we cannot determine the task configuration for any reason, it is likely that
+ // we won't be able to determine its position correctly as well. DisplayFeatures'
+ // bounds in this case can't be computed correctly, so we should skip.
+ return false;
+ }
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ final WindowManager windowManager = Objects.requireNonNull(
+ context.getSystemService(WindowManager.class));
+ final Rect currentBounds = windowManager.getCurrentWindowMetrics().getBounds();
+ final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
+ boolean isTaskExpanded = maxBounds.equals(taskBounds);
+ boolean isActivityExpanded = maxBounds.equals(currentBounds);
+ /*
+ * We need to proxy being in full screen because when a user enters PiP and exits PiP
+ * the task windowingMode will report multi-window/pinned until the transition is
+ * finished in WM Shell.
+ * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen
+ * For tasks that are letterboxed, we use currentBounds == maxBounds to filter these
+ * out.
+ */
+ // TODO(b/262900133) remove currentBounds check when letterboxed apps report bounds.
+ // currently we don't want to report to letterboxed apps since they do not update the
+ // window bounds when the Activity is moved. An inaccurate fold will be reported so
+ // we skip.
+ return isTaskExpanded && (isActivityExpanded
+ || mTaskFragmentOrganizer.isActivityEmbedded(activityToken));
} else {
// TODO(b/242674941): use task windowing mode for window context that associates with
// activity.
windowingMode = context.getResources().getConfiguration().windowConfiguration
.getWindowingMode();
}
- if (windowingMode == -1) {
- // If we cannot determine the task windowing mode for any reason, it is likely that we
- // won't be able to determine its position correctly as well. DisplayFeatures' bounds
- // in this case can't be computed correctly, so we should skip.
- return false;
- }
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return !WindowConfiguration.inMultiWindowMode(windowingMode);
}
+ @GuardedBy("mLock")
private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) {
if (isListeningForLayoutChanges(token)) {
mFoldingFeatureProducer.getData(
@@ -332,29 +391,36 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
- onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+ synchronized (mLock) {
+ onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+ }
}
@Override
public void onActivityConfigurationChanged(Activity activity) {
super.onActivityConfigurationChanged(activity);
- onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+ synchronized (mLock) {
+ onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+ }
}
}
- private final class WindowContextConfigListener implements ComponentCallbacks {
+ private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
- WindowContextConfigListener(IBinder token) {
+ ConfigurationChangeListener(IBinder token) {
mToken = token;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
- onDisplayFeaturesChangedIfListening(mToken);
+ synchronized (mLock) {
+ onDisplayFeaturesChangedIfListening(mToken);
+ }
}
@Override
- public void onLowMemory() {}
+ public void onLowMemory() {
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
index 7624b693ac43..fe60037483c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -27,9 +27,10 @@ import java.util.function.Consumer;
*/
public class AcceptOnceConsumer<T> implements Consumer<T> {
private final Consumer<T> mCallback;
- private final DataProducer<T> mProducer;
+ private final AcceptOnceProducerCallback<T> mProducer;
- public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+ public AcceptOnceConsumer(@NonNull AcceptOnceProducerCallback<T> producer,
+ @NonNull Consumer<T> callback) {
mProducer = producer;
mCallback = callback;
}
@@ -37,6 +38,20 @@ public class AcceptOnceConsumer<T> implements Consumer<T> {
@Override
public void accept(@NonNull T t) {
mCallback.accept(t);
- mProducer.removeDataChangedCallback(this);
+ mProducer.onConsumerReadyToBeRemoved(this);
+ }
+
+ /**
+ * Interface to allow the {@link AcceptOnceConsumer} to notify the client that created it,
+ * when it is ready to be removed. This allows the client to remove the consumer object
+ * when it deems it is safe to do so.
+ * @param <T> The type of data this callback accepts through {@link #onConsumerReadyToBeRemoved}
+ */
+ public interface AcceptOnceProducerCallback<T> {
+
+ /**
+ * Notifies that the given {@code callback} is ready to be removed
+ */
+ void onConsumerReadyToBeRemoved(Consumer<T> callback);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index cbaa27712015..46c925aaf8a2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,6 +19,7 @@ package androidx.window.util;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
@@ -31,11 +32,14 @@ import java.util.function.Consumer;
*
* @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
-public abstract class BaseDataProducer<T> implements DataProducer<T> {
+public abstract class BaseDataProducer<T> implements DataProducer<T>,
+ AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
+ @GuardedBy("mLock")
+ private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
/**
* Adds a callback to the set of callbacks listening for data. Data is delivered through
@@ -85,6 +89,26 @@ public abstract class BaseDataProducer<T> implements DataProducer<T> {
for (Consumer<T> callback : mCallbacks) {
callback.accept(value);
}
+ removeFinishedCallbacksLocked();
+ }
+ }
+
+ /**
+ * Removes any callbacks that notified us through {@link #onConsumerReadyToBeRemoved(Consumer)}
+ * that they are ready to be removed.
+ */
+ @GuardedBy("mLock")
+ private void removeFinishedCallbacksLocked() {
+ for (Consumer<T> callback: mCallbacksToRemove) {
+ mCallbacks.remove(callback);
+ }
+ mCallbacksToRemove.clear();
+ }
+
+ @Override
+ public void onConsumerReadyToBeRemoved(Consumer<T> callback) {
+ synchronized (mLock) {
+ mCallbacksToRemove.add(callback);
}
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 13a2c78d463e..d189ae2cf72e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -22,6 +22,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
import org.junit.Test;
@@ -53,4 +54,15 @@ public class WindowExtensionsTest {
public void testGetActivityEmbeddingComponent() {
assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
}
+
+ @Test
+ public void testSplitAttributes_default() {
+ // Make sure the default value in the extensions aar.
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+ assertThat(splitAttributes.getLayoutDirection())
+ .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
+ assertThat(splitAttributes.getSplitType())
+ .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
+ assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ }
}
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 40f7a273980a..2f92a577baa2 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
@@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
+import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -45,6 +46,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
public class EmbeddingTestUtils {
static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
static final int TASK_ID = 10;
@@ -160,15 +163,22 @@ public class EmbeddingTestUtils {
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
+ return createMockTaskFragmentInfo(container, activity, true /* isVisible */);
+ }
+
+ /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity, boolean isVisible) {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
mock(WindowContainerToken.class),
new Configuration(),
1,
- true /* isVisible */,
+ isVisible,
Collections.singletonList(activity.getActivityToken()),
new Point(),
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
new Point());
}
@@ -190,6 +200,15 @@ public class EmbeddingTestUtils {
return new TaskContainer(TASK_ID, activity);
}
+ static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final int taskId = taskContainer.getTaskId();
+ // Should not call to create TaskContainer with the same task id twice.
+ assertFalse(controller.mTaskContainers.contains(taskId));
+ controller.mTaskContainers.put(taskId, taskContainer);
+ return taskContainer;
+ }
+
static WindowLayoutInfo createWindowLayoutInfo() {
final FoldingFeature foldingFeature = new FoldingFeature(
new Rect(
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 957a24873998..bbb454d31c38 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
@@ -18,7 +18,6 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
@@ -85,42 +82,27 @@ public class JetpackTaskFragmentOrganizerTest {
@Test
public void testUnregisterOrganizer() {
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+ mOrganizer.overrideSplitAnimation();
mOrganizer.unregisterOrganizer();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
@Test
- public void testStartOverrideSplitAnimation() {
+ public void testOverrideSplitAnimation() {
assertNull(mOrganizer.mAnimationController);
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
+ mOrganizer.overrideSplitAnimation();
assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(TASK_ID,
- mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
- public void testStopOverrideSplitAnimation() {
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
}
@Test
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
container.setInfo(mTransaction, info);
@@ -144,6 +126,6 @@ public class JetpackTaskFragmentOrganizerTest {
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- new Point());
+ 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 25d034756265..81c39571bffa 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
@@ -90,6 +90,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -102,6 +103,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Test class for {@link SplitController}.
@@ -132,16 +134,30 @@ public class SplitControllerTest {
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private List<SplitInfo> mSplitInfos;
+ private TransactionManager mTransactionManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any());
- mSplitController = new SplitController(mWindowLayoutComponent);
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mSplitController = new SplitController(mWindowLayoutComponent, producer);
mSplitPresenter = mSplitController.mPresenter;
+ mSplitInfos = new ArrayList<>();
+ mEmbeddingCallback = splitInfos -> {
+ mSplitInfos.clear();
+ mSplitInfos.addAll(splitInfos);
+ };
+ mSplitController.setSplitInfoCallback(mEmbeddingCallback);
+ mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mEmbeddingCallback);
+ spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -156,7 +172,7 @@ public class SplitControllerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// tf1 has no running activity so is not active.
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
// tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount();
@@ -212,6 +228,8 @@ public class SplitControllerTest {
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -227,6 +245,14 @@ public class SplitControllerTest {
assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+ // When the activity is not finishing, do not clear the record.
+ doReturn(false).when(mActivity).isFinishing();
+ mSplitController.onActivityDestroyed(mActivity);
+
+ assertTrue(tf.hasActivity(mActivity.getActivityToken()));
+
+ // Clear the record when the activity is finishing and destroyed.
+ doReturn(true).when(mActivity).isFinishing();
mSplitController.onActivityDestroyed(mActivity);
assertFalse(tf.hasActivity(mActivity.getActivityToken()));
@@ -246,7 +272,7 @@ public class SplitControllerTest {
assertNotNull(tf);
assertNotNull(taskContainer);
- assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds());
+ assertEquals(TASK_BOUNDS, taskContainer.getConfiguration().windowConfiguration.getBounds());
}
@Test
@@ -324,11 +350,35 @@ public class SplitControllerTest {
}
@Test
+ public void testUpdateContainer_skipIfTaskIsInvisible() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ spyOn(taskContainer);
+
+ // No update when the Task is invisible.
+ clearInvocations(mSplitPresenter);
+ doReturn(false).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), 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);
+ }
+
+ @Test
public void testOnStartActivityResultError() {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- intent, taskContainer, mSplitController);
+ intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final SplitController.ActivityStartMonitor monitor =
mSplitController.getActivityStartMonitor();
@@ -562,7 +612,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
}
@Test
@@ -615,6 +665,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +699,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +733,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -718,7 +774,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -760,7 +816,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -961,6 +1017,8 @@ public class SplitControllerTest {
@Test
public void testGetPlaceholderOptions() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
doReturn(true).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,18 +1205,133 @@ public class SplitControllerTest {
+ "of other properties",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+ }
+
+ @Test
+ public void testSplitInfoCallback_reportSplit() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+ final SplitInfo splitInfo = mSplitInfos.get(0);
+ assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
+ assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
+ assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0));
+ assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0));
+ }
+
+ @Test
+ public void testSplitInfoCallback_reportSplitInMultipleTasks() {
+ final int taskId0 = 1;
+ final int taskId1 = 2;
+ final Activity r0 = createMockActivity(taskId0);
+ final Activity r1 = createMockActivity(taskId0);
+ final Activity r2 = createMockActivity(taskId1);
+ final Activity r3 = createMockActivity(taskId1);
+ addSplitTaskFragments(r0, r1);
+ addSplitTaskFragments(r2, r3);
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
+ public void testSplitInfoCallback_doNotReportIfInIntermediateState() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0);
+ final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1);
+ spyOn(tf0);
+ spyOn(tf1);
+
+ // Do not report if activity has not appeared in the TaskFragmentContainer in split.
+ doReturn(true).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback, never()).accept(any());
+
+ doReturn(false).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback).accept(any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_nonEmbeddedActivity() {
+ // Launch placeholder for non embedded activity.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT),
+ any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment() {
+ // Launch placeholder for activity in top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ assertTrue(container.hasActivity(mActivity.getActivityToken()));
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT),
+ any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment() {
+ // Do not launch placeholder for invisible activity below the top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity,
+ TASK_ID);
+ bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity,
+ false /* isVisible */));
+ topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
+ assertFalse(bottomTf.isVisible());
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment() {
+ // Launch placeholder for visible activity below the top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity,
+ TASK_ID);
+ bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity,
+ true /* isVisible */));
+ topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
+ assertTrue(bottomTf.isVisible());
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any());
}
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
+ return createMockActivity(TASK_ID);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity;
@@ -1166,7 +1339,8 @@ public class SplitControllerTest {
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
- final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
setupTaskFragmentInfo(container, activity);
return container;
}
@@ -1257,7 +1431,7 @@ public class SplitControllerTest {
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
- final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
+ final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
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 6dae0a1086b3..121e81394b2d 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
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -60,17 +61,21 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -113,7 +118,9 @@ public class SplitPresenterTest {
MockitoAnnotations.initMocks(this);
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any());
- mController = new SplitController(mWindowLayoutComponent);
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mController = new SplitController(mWindowLayoutComponent, producer);
mPresenter = mController.mPresenter;
spyOn(mController);
spyOn(mPresenter);
@@ -163,7 +170,38 @@ public class SplitPresenterTest {
WINDOWING_MODE_MULTI_WINDOW);
verify(mTransaction, never()).setWindowingMode(any(), anyInt());
+ }
+
+ @Test
+ public void testUpdateAnimationParams() {
+ final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+ // Verify the default.
+ assertTrue(container.areLastRequestedAnimationParamsEqual(
+ TaskFragmentAnimationParams.DEFAULT));
+
+ final int bgColor = Color.GREEN;
+ final TaskFragmentAnimationParams animationParams =
+ new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(bgColor)
+ .build();
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+
+ final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(),
+ expectedOperation);
+ assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams));
+
+ // No request to set the same animation params.
+ clearInvocations(mTransaction);
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+ verify(mTransaction, never()).setTaskFragmentOperation(any(), any());
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index af9c6ba5c162..13e709271221 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -23,7 +23,6 @@ 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 androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static org.junit.Assert.assertEquals;
@@ -68,28 +67,6 @@ public class TaskContainerTest {
}
@Test
- public void testIsTaskBoundsInitialized() {
- final TaskContainer taskContainer = createTestTaskContainer();
-
- assertFalse(taskContainer.isTaskBoundsInitialized());
-
- taskContainer.setTaskBounds(TASK_BOUNDS);
-
- assertTrue(taskContainer.isTaskBoundsInitialized());
- }
-
- @Test
- public void testSetTaskBounds() {
- final TaskContainer taskContainer = createTestTaskContainer();
-
- assertFalse(taskContainer.setTaskBounds(new Rect()));
-
- assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS));
-
- assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS));
- }
-
- @Test
public void testGetWindowingModeForSplitTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
final Rect splitBounds = new Rect(0, 0, 500, 1000);
@@ -145,7 +122,7 @@ public class TaskContainerTest {
assertTrue(taskContainer.isEmpty());
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertFalse(taskContainer.isEmpty());
@@ -161,11 +138,11 @@ public class TaskContainerTest {
assertNull(taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342bfb309..379ea0c534ba 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
package androidx.window.extensions.embedding;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@ public class TaskFragmentAnimationControllerTest {
@Test
public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
// No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
}
@Test
public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+ verify(mOrganizer, never()).unregisterRemoteAnimations();
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).unregisterRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- }
-
- @Test
- public void testUnregisterAllRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.registerRemoteAnimations(TASK_ID + 1);
- mAnimationController.unregisterAllRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 35415d816d8b..7d9d8b0f3a06 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -45,6 +45,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.google.android.collect.Lists;
@@ -82,7 +84,10 @@ public class TaskFragmentContainerTest {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mController = new SplitController();
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ WindowLayoutComponentImpl component = mock(WindowLayoutComponentImpl.class);
+ mController = new SplitController(component, producer);
spyOn(mController);
mActivity = createMockActivity();
mIntent = new Intent();
@@ -94,18 +99,21 @@ public class TaskFragmentContainerTest {
// One of the activity and the intent must be non-null
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(null, null, taskContainer, mController));
+ () -> new TaskFragmentContainer(null, null, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
// One of the activity and the intent must be null.
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController));
+ () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
}
@Test
public void testFinish() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container).when(mController).getContainerWithActivity(mActivity);
// Only remove the activity, but not clear the reference until appeared.
@@ -137,12 +145,14 @@ public class TaskFragmentContainerTest {
public void testFinish_notFinishActivityThatIsReparenting() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -159,9 +169,11 @@ public class TaskFragmentContainerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side.
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
- assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(mActivity));
+ assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
+ mActivity.getActivityToken()));
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
mActivity);
@@ -171,7 +183,8 @@ public class TaskFragmentContainerTest {
// Pending intent should be cleared when the container becomes non-empty.
final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
- null /* pendingAppearedActivity */, mIntent, taskContainer, mController);
+ null /* pendingAppearedActivity */, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
@@ -186,7 +199,7 @@ public class TaskFragmentContainerTest {
public void testIsWaitingActivityAppear() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container.isWaitingActivityAppear());
@@ -208,7 +221,7 @@ public class TaskFragmentContainerTest {
doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertNull(container.mAppearEmptyTimeout);
@@ -248,7 +261,7 @@ public class TaskFragmentContainerTest {
public void testCollectNonFinishingActivities() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
List<Activity> activities = container.collectNonFinishingActivities();
assertTrue(activities.isEmpty());
@@ -276,7 +289,7 @@ public class TaskFragmentContainerTest {
public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(1, container.collectNonFinishingActivities().size());
@@ -290,9 +303,9 @@ public class TaskFragmentContainerTest {
public void testIsAbove() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container1.isAbove(container0));
assertFalse(container0.isAbove(container1));
@@ -302,7 +315,7 @@ public class TaskFragmentContainerTest {
public void testGetBottomMostActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(mActivity, container.getBottomMostActivity());
@@ -317,9 +330,9 @@ public class TaskFragmentContainerTest {
@Test
public void testOnActivityDestroyed() {
- final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
final List<IBinder> activities = new ArrayList<>();
activities.add(mActivity.getActivityToken());
@@ -328,12 +341,193 @@ public class TaskFragmentContainerTest {
assertTrue(container.hasActivity(mActivity.getActivityToken()));
- taskContainer.onActivityDestroyed(mActivity);
+ taskContainer.onActivityDestroyed(mActivity.getActivityToken());
// It should not contain the destroyed Activity.
assertFalse(container.hasActivity(mActivity.getActivityToken()));
}
+ @Test
+ public void testIsInIntermediateState() {
+ // True if no info set.
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ spyOn(taskContainer);
+ doReturn(true).when(taskContainer).isVisible();
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if empty info set.
+ final List<IBinder> activities = new ArrayList<>();
+ doReturn(activities).when(mInfo).getActivities();
+ doReturn(true).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if info is not empty.
+ doReturn(false).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is pending appeared activity.
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if the activity is finishing.
+ activities.add(mActivity.getActivityToken());
+ doReturn(true).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if the activity is not finishing.
+ doReturn(false).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is a token that can't find associated activity.
+ activities.clear();
+ activities.add(new Binder());
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if there is a token that can't find associated activity when the Task is invisible.
+ doReturn(false).when(taskContainer).isVisible();
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+ }
+
+ @Test
+ public void testHasAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ container.addPendingAppearedActivity(mActivity);
+
+ assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.hasAppearedActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testHasPendingAppearedActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testHasActivity() {
+ final TaskContainer taskContainer = createTestTaskContainer(mController);
+ final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+ final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+
+ // Activity is pending appeared on container2.
+ container2.addPendingAppearedActivity(mActivity);
+
+ assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+
+ // Activity is pending appeared on container1 (removed from container2).
+ container1.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+ assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+ final List<IBinder> activities = new ArrayList<>();
+ activities.add(mActivity.getActivityToken());
+ doReturn(activities).when(mInfo).getActivities();
+
+ // Although Activity is appeared on container2, we prioritize pending appeared record on
+ // container1.
+ container2.setInfo(mTransaction, mInfo);
+
+ assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+ assertFalse(container2.hasActivity(mActivity.getActivityToken()));
+
+ // When the pending appeared record is removed from container1, we respect the appeared
+ // record in container2.
+ container1.removePendingAppearedActivity(mActivity.getActivityToken());
+
+ assertFalse(container1.hasActivity(mActivity.getActivityToken()));
+ assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+ }
+
+ @Test
+ public void testNewContainerWithPairedPrimaryContainer() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ taskContainer.mContainers.add(tf0);
+ taskContainer.mContainers.add(tf1);
+
+ // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
+ // right above tf0.
+ final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0);
+ assertEquals(0, taskContainer.indexOf(tf0));
+ assertEquals(1, taskContainer.indexOf(tf2));
+ assertEquals(2, taskContainer.indexOf(tf1));
+ }
+
+ @Test
+ public void testIsVisible() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+
+ // Not visible when there is not appeared.
+ assertFalse(container.isVisible());
+
+ // Respect info.isVisible.
+ TaskFragmentInfo info = createMockTaskFragmentInfo(container, mActivity,
+ true /* isVisible */);
+ container.setInfo(mTransaction, info);
+
+ assertTrue(container.isVisible());
+
+ info = createMockTaskFragmentInfo(container, mActivity, false /* isVisible */);
+ container.setInfo(mTransaction, info);
+
+ assertFalse(container.isVisible());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
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
new file mode 100644
index 000000000000..62006bd51399
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TransactionManager}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:TransactionManagerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransactionManagerTest {
+
+ @Mock
+ private TaskFragmentOrganizer mOrganizer;
+ private TransactionManager mTransactionManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTransactionManager = new TransactionManager(mOrganizer);
+ }
+
+ @Test
+ public void testStartNewTransaction() {
+ mTransactionManager.startNewTransaction();
+
+ // Throw exception if #startNewTransaction is called twice without #apply() or #abort().
+ assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction);
+
+ // Allow to start new after #apply() the last transaction.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ // Allow to start new after #abort() the last transaction.
+ transactionRecord.abort();
+ mTransactionManager.startNewTransaction();
+ }
+
+ @Test
+ public void testSetTransactionOriginType() {
+ // Return TRANSIT_CHANGE if there is no trigger type set.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+
+ // Return the first set type.
+ mTransactionManager.getCurrentTransactionRecord().abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ // Reset when #startNewTransaction().
+ transactionRecord.abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+ }
+
+ @Test
+ public void testGetCurrentTransactionRecord() {
+ // Throw exception if #getTransaction is called without calling #startNewTransaction().
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ assertNotNull(transactionRecord);
+
+ // Same WindowContainerTransaction should be returned.
+ assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #abort().
+ transactionRecord.abort();
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ // New WindowContainerTransaction after #startNewTransaction().
+ mTransactionManager.startNewTransaction();
+ assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #apply().
+ mTransactionManager.getCurrentTransactionRecord().apply(
+ false /* shouldApplyIndependently */);
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+ }
+
+ @Test
+ public void testApply() {
+ // #applyTransaction(false)
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ int transitionType = transactionRecord.getTransactionTransitionType();
+ WindowContainerTransaction wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #applyTransaction(true)
+ clearInvocations(mOrganizer);
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(false)
+ clearInvocations(mOrganizer);
+ IBinder token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(true)
+ clearInvocations(mOrganizer);
+ token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // Throw exception if there is any more interaction.
+ final TransactionRecord record = transactionRecord;
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ record::abort);
+ }
+
+ @Test
+ public void testAbort() {
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.abort();
+
+ // Throw exception if there is any more interaction.
+ verifyNoMoreInteractions(mOrganizer);
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ transactionRecord::abort);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 2c766d81d611..84ab4487feee 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec5080b..f615ad6e671b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,10 @@ filegroup {
srcs: [
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+ "src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
],
path: "src",
}
@@ -100,6 +104,21 @@ genrule {
out: ["wm_shell_protolog.json"],
}
+genrule {
+ name: "protolog.json.gz",
+ srcs: [":generate-wm_shell_protolog.json"],
+ out: ["wmshell.protolog.json.gz"],
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
+}
+
+prebuilt_etc {
+ name: "wmshell.protolog.json.gz",
+ system_ext_specific: true,
+ src: ":protolog.json.gz",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -123,9 +142,6 @@ android_library {
resource_dirs: [
"res",
],
- java_resources: [
- ":generate-wm_shell_protolog.json",
- ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml
index 1ecc13e4da38..1ecc13e4da38 100644
--- a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
+++ b/libs/WindowManager/Shell/res/color/decor_title_color.xml
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 000000000000..8779cc09715b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 000000000000..ea0fbb0e5d33
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 000000000000..c55cbe2d054c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 000000000000..447df43dfddd
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 000000000000..c334a543a86a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:translateX="6.0"
+ android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
new file mode 100644
index 000000000000..5ecba380fb60
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ >
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="4.0"
+ android:translateY="4.0" >
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/>
+
+ </group>
+</vector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
index f2f1a1d55dee..cf9e632f6941 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -18,15 +18,13 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
- android:viewportHeight="32.0"
- android:tint="@color/decor_button_dark_color"
- >
+ android:viewportHeight="32.0">
<group android:scaleX="0.5"
android:scaleY="0.5"
- android:translateX="8.0"
- android:translateY="8.0" >
+ android:translateX="4.0"
+ android:translateY="4.0" >
<path
- android:fillColor="@android:color/white"
- android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ android:fillColor="@android:color/black"
+ android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
new file mode 100644
index 000000000000..c9f262398f68
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index 8207365a737d..416287d2cbb3 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
@@ -15,8 +15,7 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_caption_title_color"
xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorPrimary" />
+ <solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
new file mode 100644
index 000000000000..416287d2cbb3
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.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 android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 000000000000..e307f007e4a4
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="210.0dp"
+ android:height="64.0dp"
+ android:tint="@color/decor_button_light_color"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae28207970..29945937788b 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ ~ 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="48dp"
android:height="48dp"
@@ -25,13 +25,12 @@
android:fillAlpha="0.8"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
<group
- android:translateX="12"
- android:translateY="12">
+ android:scaleX="0.8"
+ android:scaleY="0.8"
+ android:translateX="10"
+ android:translateY="10">
<path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
- <path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+ android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+ android:fillColor="@color/compat_controls_text"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
deleted file mode 100644
index d183e42c173b..000000000000
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/caption"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="end"
- android:background="@drawable/decor_caption_title">
- <Button
- android:id="@+id/minimize_window"
- android:visibility="gone"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
- android:layout_gravity="top|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_minimize_button_dark"
- android:duplicateParentState="true"/>
- <Button
- android:id="@+id/maximize_window"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
- android:layout_gravity="center_vertical|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_maximize_button_dark"
- android:duplicateParentState="true"/>
- <Button
- android:id="@+id/close_window"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
- android:layout_gravity="center_vertical|end"
- android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"
- android:duplicateParentState="true"/>
-</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
new file mode 100644
index 000000000000..8b4792acba3e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/desktop_mode_decor_menu_background">
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/fullscreen_button"
+ android:contentDescription="@string/fullscreen_text"
+ android:background="@drawable/caption_fullscreen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/split_screen_button"
+ android:contentDescription="@string/split_screen_text"
+ android:background="@drawable/caption_split_screen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/floating_button"
+ android:contentDescription="@string/float_button_text"
+ android:background="@drawable/caption_floating_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/desktop_button"
+ android:contentDescription="@string/desktop_text"
+ android:background="@drawable/caption_desktop_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/more_button"
+ android:contentDescription="@string/more_button_text"
+ android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
new file mode 100644
index 000000000000..2a4cc02f0925
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/desktop_mode_caption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/desktop_mode_decor_title">
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/back_button"
+ android:contentDescription="@string/back_button_text"
+ android:background="@drawable/decor_back_button_dark"
+ />
+ <Button
+ android:id="@+id/caption_handle"
+ android:layout_width="128dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:contentDescription="@string/handle_text"
+ android:background="@drawable/decor_handle_dark"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/close_window"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ 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 cc600741d1b7..2476f65c7e5b 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tik om hierdie program te herbegin vir ’n beter aansig."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tik om hierdie program te herbegin en maak volskerm oop."</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="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_dialog_title" msgid="6688664582871779215">"Sommige programme werk beter in portret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van hierdie opsies om jou spasie ten beste te benut"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai jou toestel om dit volskerm te maak"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik langs ’n program om dit te herposisioneer"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
index 6187ea46769c..c87bec093cca 100644
--- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Maak toe"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</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_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="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 7aa877041d53..f0c391cd6b99 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ለተሻለ ዕይታ ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ።"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ይህን መተግበሪያ ዳግም ለማስነሳት መታ ያድርጉ እና ወደ ሙሉ ማያ ገጽ ይሂዱ።"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"የካሜራ ችግሮች አሉ?\nዳግም ለማበጀት መታ ያድርጉ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</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_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index 74ce49ef078e..d23353858de6 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ዝጋ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</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_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="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 2d0d9705cab1..aa4b3b704110 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"انقر لإعادة تشغيل هذا التطبيق للحصول على عرض أفضل."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"انقر لإعادة تشغيل هذا التطبيق والانتقال إلى وضع ملء الشاشة."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل هناك مشاكل في الكاميرا؟\nانقر لإعادة الضبط."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</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_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
index 9c195a7386a9..a1ceda5fc987 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"إغلاق"</string>
+ <string name="pip_close" msgid="9135220303720555525">"‏إغلاق PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</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_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="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 cd184297d2cf..985d3b9b96fd 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -37,14 +37,14 @@
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
- <string name="accessibility_action_divider_left_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="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"উন্নত ভিউৰ বাবে এপ্‌টো ৰিষ্টাৰ্ট কৰিবলৈ টিপক।"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"এপ্‌টো ৰিষ্টাৰ্ট কৰিবলৈ আৰু পূৰ্ণ স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ টিপক।"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো সমস্যা হৈছে নেকি?\nপুনৰ খাপ খোৱাবলৈ টিপক"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"কিছুমান এপে প’ৰ্ট্ৰেইট ম’ডত বেছি ভালকৈ কাম কৰে"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"আপোনাৰ spaceৰ পৰা পাৰ্যমানে উপকৃত হ’বলৈ ইয়াৰে এটা বিকল্প চেষ্টা কৰি চাওক"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"পূৰ্ণ স্ক্ৰীনলৈ যাবলৈ আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ কাষত দুবাৰ টিপক"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
index 816b5b1c79dc..8d7bd9f6a27e 100644
--- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"বন্ধ কৰক"</string>
+ <string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</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_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="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 5da5a97ff076..8cd9b7a635ab 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toxunaraq bu tətbiqi yenidən başladın ki, daha görüntü əldə edəsiniz."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Bu tətbiqi sıfırlayaraq tam ekrana keçmək üçün toxunun."</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="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_dialog_title" msgid="6688664582871779215">"Bəzi tətbiqlər portret rejimində daha yaxşı işləyir"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Məkanınızdan maksimum yararlanmaq üçün bu seçimlərdən birini sınayın"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana keçmək üçün cihazınızı fırladın"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tətbiqin yerini dəyişmək üçün yanına iki dəfə toxunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
index ccb7a7069ad8..87c46fa41a01 100644
--- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Bağlayın"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
- <string name="pip_move" msgid="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_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="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 aff2b9f686c8..49524c608543 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste restartovali aplikaciju i prešli u režim celog ekrana."</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="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_dialog_title" msgid="6688664582871779215">"Neke aplikacije najbolje funkcionišu u uspravnom režimu"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste na najbolji način iskoristili prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotirajte uređaj za prikaz preko celog ekrana"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste promenili njenu poziciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</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>
</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 51a1262b1de7..c87f30611a07 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
@@ -19,16 +19,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
- <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</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_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="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 e325e511596c..1767e0d66241 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Націсніце, каб перазапусціць гэту праграму для лепшага прагляду."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Націсніце, каб перазапусціць гэту праграму і перайсці ў поўнаэкранны рэжым."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Праблемы з камерай?\nНацісніце, каб пераабсталяваць"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</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_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index 15a353c649d6..3566bc372820 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Закрыць"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</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_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="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 3bedc86a79ac..c22fb86a4d4d 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Докоснете, за да рестартирате това приложение с цел по-добър изглед."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Докоснете, за да рестартирате това приложение в режим на цял екран."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблеми с камерата?\nДокоснете за ремонтиране"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</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_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
index 2b27a6927077..91049fd2cf02 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Затваряне"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</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_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="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 35e5b38233cc..c0944e0584e6 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"আরও ভাল ভিউয়ের জন্য এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন।"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"এই অ্যাপ রিস্টার্ট করতে ট্যাপ করুন ও \'ফুল-স্ক্রিন\' মোড ব্যবহার করুন।"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ক্যামেরা সংক্রান্ত সমস্যা?\nরিফিট করতে ট্যাপ করুন"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</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_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
index 23c8ffabeede..792708d128a5 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"বন্ধ করুন"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</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_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="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 34b282b24666..ae01c641cc43 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -35,7 +35,7 @@
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik 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>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da ponovo pokrenete ovu aplikaciju i aktivirate prikaz preko cijelog ekrana."</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="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_dialog_title" msgid="6688664582871779215">"Određene aplikacije najbolje funkcioniraju u uspravnom načinu rada"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da maksimalno iskoristite prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zarotirajte uređaj da aktivirate prikaz preko cijelog ekrana"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da promijenite njen položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
index 443fd620fd65..b7f0dca1b5a5 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
@@ -19,16 +19,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
- <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string>
- <string name="pip_move" msgid="158770205886688553">"Premjesti"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Proširi"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Suzi"</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="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 0279917272a5..8a522b3e6397 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Toca per reiniciar aquesta aplicació i passar a pantalla completa."</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="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_dialog_title" msgid="6688664582871779215">"Algunes aplicacions funcionen millor en posició vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una d\'aquestes opcions per treure el màxim profit de l\'espai"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositiu per passar a pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Fes doble toc al costat d\'una aplicació per canviar-ne la posició"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index 94ba0db7e978..1c560c7afa06 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -19,16 +19,10 @@
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">"(Programa sense títol)"</string>
- <string name="pip_close" msgid="2955969519031223530">"Tanca"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</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_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="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla en pantalla."</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 d58100b5bb9b..d0cf80aef38c 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Klepnutím aplikaci restartujete a přejdete na režim celé obrazovky"</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="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_dialog_title" msgid="6688664582871779215">"Některé aplikace fungují nejlépe na výšku"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pokud chcete maximálně využít prostor, vyzkoušejte jednu z těchto možností"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zařízení přejděte do režimu celé obrazovky"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedle aplikace změňte její umístění"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
index 3ed85dce0433..9a8cc2b4d70e 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Zavřít"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
- <string name="pip_move" msgid="158770205886688553">"Přesunout"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Rozbalit"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Sbalit"</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="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 abb9cea6c6f3..bb81c10c6e1b 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tryk for at genstarte denne app, så visningen forbedres."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tryk for at genstarte denne app, og gå til fuld skærm."</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="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_dialog_title" msgid="6688664582871779215">"Nogle apps fungerer bedst i stående format"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv én af disse muligheder for at få mest muligt ud af dit rum"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Drej din enhed for at gå til fuld skærm"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryk to gange ud for en app for at ændre dens placering"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
index 09024428a825..cba660ac723c 100644
--- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Luk"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string>
- <string name="pip_move" msgid="158770205886688553">"Flyt"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Udvid"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Skjul"</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="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 a579394818b9..c5d945a982ef 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tippe, um diese App neu zu starten und die Ansicht zu verbessern."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tippe, um die App im Vollbildmodus neu zu starten."</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="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_dialog_title" msgid="6688664582871779215">"Einige Apps funktionieren am besten im Hochformat"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Mithilfe dieser Möglichkeiten kannst du dein Display optimal nutzen"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gerät drehen, um zum Vollbildmodus zu wechseln"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Neben einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 18535c9d9338..02a1b66eb63f 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Schließen"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</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_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="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 248cff818da6..70f55058925c 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Πατήστε για να επανεκκινήσετε αυτή την εφαρμογή για καλύτερη προβολή."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Πατήστε για επανεκκίνηση αυτής της εφαρμογής και ενεργοποίηση πλήρους οθόνης."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nΠατήστε για επιδιόρθωση."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</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_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
index 5f8a004b0a1f..24cd030cd754 100644
--- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Κλείσιμο"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</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_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="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 9c798b84c814..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</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="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_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</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>
</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 839789b22a1c..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Close"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</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_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="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 30e2235cc6fd..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -35,7 +35,7 @@
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<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="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>
@@ -64,24 +64,21 @@
<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 anytime"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</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">"Got it"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</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="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</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="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_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</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>
</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 5a811e193cb4..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -17,18 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="notification_channel_tv_pip" msgid="2576686079160402435">"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="2955969519031223530">"Close"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</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="3672999496647508701">" 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>
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Expand PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Collapse PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 9c798b84c814..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</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="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_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</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>
</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 839789b22a1c..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Close"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</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_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="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 9c798b84c814..0b5aefa5c72e 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tap to restart this app for a better view."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tap to restart this app and go full screen."</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="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_dialog_title" msgid="6688664582871779215">"Some apps work best in portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Try one of these options to make the most of your space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</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>
</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 839789b22a1c..82257b42814d 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Close"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</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_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="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 5ff5c586b38f..5c3d0f65374a 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎Tap to restart this app for a better view.‎‏‎‎‏‎"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎Tap to restart this app and go full screen.‎‏‎‎‏‎"</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="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_dialog_title" msgid="6688664582871779215">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎Some apps work best in portrait‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎Try one of these options to make the most of your space‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎Rotate your device to go full screen‎‏‎‎‏‎"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎Double-tap next to an app to reposition it‎‏‎‎‏‎"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</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>
</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 507e066e3812..a6e494cfed3c 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎Close‎‏‎‎‏‎"</string>
+ <string name="pip_close" msgid="9135220303720555525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎Close PIP‎‏‎‎‏‎"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎Full screen‎‏‎‎‏‎"</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_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="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 08a7f49f3dfd..e523ae53b0cc 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Presiona para reiniciar esta app y tener una mejor vista."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Presiona para reiniciar esta app y acceder al modo de pantalla completa."</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="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_dialog_title" msgid="6688664582871779215">"Algunas apps funcionan mejor en modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba estas opciones para aprovechar al máximo tu espacio"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rota el dispositivo para ver la pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Presiona dos veces junto a una app para cambiar su posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
- <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
- <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
</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 a2c27b79e04c..458f6b15b857 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Cerrar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</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_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="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 139232cc808f..974960708190 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -46,10 +46,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="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</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>
@@ -63,7 +63,7 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatea con burbujas"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamados \"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 llamadas \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controla las burbujas"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toca Gestionar para desactivar las burbujas de esta aplicación"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Toca para reiniciar esta aplicación e ir a la pantalla completa."</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="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_dialog_title" msgid="6688664582871779215">"Algunas aplicaciones funcionan mejor en vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prueba una de estas opciones para sacar el máximo partido al espacio de tu pantalla"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositivo para ir al modo de pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dos veces junto a una aplicación para cambiar su posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
- <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
- <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 75db421ec405..0a690984dac5 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Cerrar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</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_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="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 d6f35807d290..a5f82a6452c4 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Puudutage, et see rakendus parema vaate jaoks taaskäivitada."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Puudutage rakenduse taaskäivitamiseks ja täisekraanrežiimi aktiveerimiseks."</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="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_dialog_title" msgid="6688664582871779215">"Mõni rakendus töötab kõige paremini vertikaalpaigutuses"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proovige ühte neist valikutest, et oma ruumi parimal moel kasutada"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pöörake seadet, et aktiveerida täisekraanirežiim"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Topeltpuudutage rakenduse kõrval, et selle asendit muuta"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
index e8fcb180c0c4..dc0232303a70 100644
--- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Sule"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</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_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="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 e0efadd6f735..caa335a96222 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -23,7 +23,7 @@
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Sartu pantaila zatituan"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menua"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"Pantaila txiki gainjarrian dago <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"<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_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_play" msgid="3496151081459417097">"Erreproduzitu"</string>
<string name="pip_pause" msgid="690688849510295232">"Pausatu"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Joan hurrengora"</string>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Hobeto ikusteko, sakatu hau aplikazioa berrabiarazteko."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Saka ezazu aplikazioa berrabiarazteko, eta ezarri pantaila osoko modua."</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="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_dialog_title" msgid="6688664582871779215">"Aplikazio batzuk orientazio bertikalean funtzionatzen dute hobekien"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pantailako eremuari ahalik eta etekinik handiena ateratzeko, probatu aukera hauetakoren bat"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pantaila osoko modua erabiltzeko, biratu gailua"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren ondoko edozein toki"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
index 07d75d2de9cd..bce06da2c66f 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Itxi"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</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_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="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 267dd2050cfa..761fb9ddeb2f 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراه‌اندازی شود."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"برای بازراه‌اندازی این برنامه و تغییر به حالت تمام‌صفحه، ضربه بزنید."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</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_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index 03f51d01a3a8..ff9a03c6cefb 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"بستن"</string>
+ <string name="pip_close" msgid="9135220303720555525">"‏بستن PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</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_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="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 30321a20c643..c809b4879e71 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Napauta, niin sovellus käynnistyy uudelleen paremmin näytölle sopivana."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Napauta, niin sovellus käynnistyy uudelleen ja siirtyy koko näytön tilaan."</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="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_dialog_title" msgid="6688664582871779215">"Osa sovelluksista toimii parhaiten pystytilassa"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Kokeile jotakin näistä vaihtoehdoista, jotta saat parhaan hyödyn näytön tilasta"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Käännä laitetta, niin se siirtyy koko näytön tilaan"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kaksoisnapauta sovellusta, jos haluat siirtää sitä"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
index 24ab7d99e180..3e8bf9032780 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Sulje"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string>
- <string name="pip_move" msgid="158770205886688553">"Siirrä"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Laajenna"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Tiivistä"</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="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 12789975fb5e..62b2bb65a603 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Touchez pour redémarrer cette application et passer en plein écran."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
- <string name="letterbox_education_split_screen_text" msgid="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_dialog_title" msgid="6688664582871779215">"Certaines applications fonctionnent mieux en mode portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour tirer le meilleur parti de votre espace"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter votre appareil pour passer en plein écran"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</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>
</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 87651ec711d9..66e13b89c64b 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Fermer"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
- <string name="pip_move" msgid="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_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="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 84c861896443..b3e22af0a3e3 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -63,7 +63,7 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatter en utilisant des bulles"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou de bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Contrô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,16 +72,13 @@
<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="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Appuyez pour redémarrer cette application et activer le mode plein écran."</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="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_dialog_title" msgid="6688664582871779215">"Certaines applis fonctionnent mieux en mode Portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Essayez l\'une de ces options pour exploiter pleinement l\'espace"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter l\'appareil pour passer en plein écran"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Appuyez deux fois à côté d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
index 37863fb82295..ed9baf5b6215 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Fermer"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
- <string name="pip_move" msgid="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_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="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 59ec2deab17c..b8e039602243 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización."</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="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="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_dialog_title" msgid="6688664582871779215">"Algunhas aplicacións funcionan mellor en modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Proba unha destas opcións para sacar o máximo proveito do espazo da pantalla"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xira o dispositivo para ver o contido en pantalla completa"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dúas veces a carón dunha aplicación para cambiala de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
- <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
- <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
index 5d6de76c4deb..a057434d7853 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Pechar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</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_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="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 85d98ed2fc7b..deda2d755e20 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"વધુ સારા વ્યૂ માટે, આ ઍપને ફરી શરૂ કરવા ટૅપ કરો."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"આ ઍપ ફરીથી ચાલુ કરવા માટે ટૅપ કરીને પૂર્ણ સ્ક્રીન કરો."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં સમસ્યાઓ છે?\nફરીથી ફિટ કરવા માટે ટૅપ કરો"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</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_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
index 6c1b9db73582..d9525910e4c6 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"બંધ કરો"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</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_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="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 bd90b7ea9220..36b11514c7e5 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -65,23 +65,20 @@
<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="6712141648865547958">"टैप करके ऐप्लिकेशन को रीस्टार्ट करें और बेहतर व्यू पाएं."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्या कैमरे से जुड़ी कोई समस्या है?\nफिर से फ़िट करने के लिए टैप करें"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</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_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index e0227253b2dc..d897ac73f80d 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"बंद करें"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्‍क्रीन"</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_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="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 41df0df1ee87..5ecc5585a6e9 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Dodirnite da biste ponovo pokrenuli tu aplikaciju i prikazali je na cijelom zaslonu."</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="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_dialog_title" msgid="6688664582871779215">"Neke aplikacije najbolje funkcioniraju u portretnom usmjerenju"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Isprobajte jednu od ovih opcija da biste maksimalno iskoristili prostor"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zakrenite uređaj radi prikaza na cijelom zaslonu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste joj promijenili položaj"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
index a09e6e805f63..8f5f3164c4d7 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
@@ -19,16 +19,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"Slika u slici"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
- <string name="pip_close" msgid="2955969519031223530">"Zatvori"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</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_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="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 9736a69f4001..2295250e2853 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"A jobb nézet érdekében koppintson az alkalmazás újraindításához."</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="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="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_dialog_title" msgid="6688664582871779215">"Egyes alkalmazások álló tájolásban működnek a leghatékonyabban"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Próbálja ki az alábbi beállítások egyikét, hogy a legjobban ki tudja használni képernyő területét"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"A teljes képernyős mód elindításához forgassa el az eszközt"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Koppintson duplán az alkalmazás mellett az áthelyezéséhez"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
index 5e065c2ad4e7..fc8d79589121 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Bezárás"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string>
- <string name="pip_move" msgid="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_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="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 3c6449421dec..208936539094 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Հպեք՝ հավելվածը վերագործարկելու և ավելի հարմար տեսք ընտրելու համար։"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Հպեք՝ հավելվածը վերագործարկելու և լիաէկրան ռեժիմին անցնելու համար։"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</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_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
index 7963abf8972b..f5665b8dd166 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Փակել"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</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_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="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 61f39036320c..1b46b2fe2570 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Ketuk untuk memulai ulang aplikasi ini dan membuka layar penuh."</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="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_dialog_title" msgid="6688664582871779215">"Beberapa aplikasi berfungsi paling baik dalam mode potret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Coba salah satu opsi berikut untuk mengoptimalkan area layar Anda"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar perangkat untuk tampilan layar penuh"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketuk dua kali di samping aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
index 7d37154bb86c..a1535653f679 100644
--- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Tutup"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</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_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="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 044348c94a95..a201c95137f3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Ýttu til að endurræsa forritið og sýna það á öllum skjánum."</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="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_dialog_title" msgid="6688664582871779215">"Sum forrit virka best í skammsniði"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prófaðu einhvern af eftirfarandi valkostum til að nýta plássið sem best"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Snúðu tækinu til að nota allan skjáinn"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ýttu tvisvar við hlið forritsins til að færa það"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
index 1490cb98e034..70ca1afe3aea 100644
--- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Loka"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string>
- <string name="pip_move" msgid="158770205886688553">"Færa"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Stækka"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Minnka"</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="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 ca97693481cb..7157ed088d30 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tocca per riavviare quest\'app per una migliore visualizzazione."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tocca per riavviare l\'app e passare alla modalità a schermo intero."</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="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_dialog_title" msgid="6688664582871779215">"Alcune app funzionano in modo ottimale in verticale"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prova una di queste opzioni per ottimizzare lo spazio a tua disposizione"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ruota il dispositivo per passare alla modalità a schermo intero"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tocca due volte accanto a un\'app per riposizionarla"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
index a48516f2588e..cda627517872 100644
--- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Chiudi"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</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_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="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 67de8a0fde34..52a6b0676222 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"צריך להקיש כדי להפעיל מחדש את האפליקציה הזו ולעבור למסך מלא."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</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_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 2af1896d3c67..30ce97b998ca 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"סגירה"</string>
+ <string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</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_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="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 cc8527e73636..5a25c24ba034 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"タップしてこのアプリを再起動すると、全画面表示になります。"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"アプリによっては縦向きにすると正常に動作します"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"スペースを最大限に活用するには、以下の方法のいずれかをお試しください"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"全画面表示にするにはデバイスを回転させてください"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"位置を変えるにはアプリの横をダブルタップしてください"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
index bc7dcb7aa029..e58e7bf6fabc 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"閉じる"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</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_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="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 0ef4563b7a8a..bff86fa6ffe2 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"შეეხეთ, რომ გადატვირთოთ ეს აპი უკეთესი ხედისთვის."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"შეეხეთ ამ აპის გადასატვირთად და გადადით სრულ ეკრანზე."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"კამერად პრობლემები აქვს?\nშეეხეთ გამოსასწორებლად"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</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_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
index 898dac2aca88..b09686646c8b 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"დახურვა"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</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_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="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 6ba9a68943cf..f57f3f581c85 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Бұл қолданбаны қайта қосып, толық экранға өту үшін түртіңіз."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</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_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
index cdf564fb4ca0..7bade0dff0d9 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Жабу"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</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_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="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 03682d800e50..5c04f881fe0e 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ចុចដើម្បី​ចាប់ផ្ដើម​កម្មវិធី​នេះឡើងវិញសម្រាប់ទិដ្ឋភាពកាន់តែប្រសើរ។"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ចុចដើម្បី​ចាប់ផ្ដើម​កម្មវិធី​នេះឡើងវិញ រួចចូលប្រើ​ពេញអេក្រង់។"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"មានបញ្ហា​ពាក់ព័ន្ធនឹង​កាមេរ៉ាឬ?\nចុចដើម្បី​ដោះស្រាយ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបាន​ដោះស្រាយ​បញ្ហានេះទេឬ?\nចុចដើម្បី​ត្រឡប់"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមាន​បញ្ហាពាក់ព័ន្ធនឹង​កាមេរ៉ាទេឬ? ចុចដើម្បី​ច្រានចោល។"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដង​នៅ​ក្រៅ​កម្មវិធី ដើម្បី​ប្ដូរ​ទីតាំង​កម្មវិធី​នោះ"</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_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
index 1a7ae813c1d3..721be1fc1650 100644
--- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"បិទ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</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_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="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 167a38e09c60..e91383caa009 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿವೆಯೇ?\nಮರುಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</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_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 45de068c80a0..8310c8a1169c 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ಮುಚ್ಚಿರಿ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</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_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="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 1175a0e884a4..104ba3f22c96 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"탭하여 이 앱을 다시 시작하고 전체 화면으로 이동합니다."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</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_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
index 9e8f1f1258a5..a3e055a515a1 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"닫기"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</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_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="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 b047863bdcf6..8203622a33fc 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Жакшыраак көрүү үчүн бул колдонмону өчүрүп күйгүзүңүз. Ал үчүн таптап коюңуз."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Бул колдонмону өчүрүп күйгүзүп, толук экранга өтүү үчүн таптап коюңуз."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада маселелер келип чыктыбы?\nОңдоо үчүн таптаңыз"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</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_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
index 19fac5876bb0..887ac52c8e43 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Жабуу"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</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_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="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 00bf7f4c7983..24396786f9d8 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ເພື່ອມຸມມອງທີ່ດີຂຶ້ນ."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ແຕະເພື່ອຣີສະຕາດແອັບນີ້ ແລະ ໃຊ້ແບບເຕັມຈໍ."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ?\nແຕະເພື່ອປັບໃໝ່"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອ​ປິດ​ໄວ້."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</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_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
index 6cd0f37c516c..91c4a033356d 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ປິດ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</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_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="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 39817930f308..e2ae643ad308 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Palieskite, kad iš naujo paleistumėte šią programą ir matytumėte aiškiau."</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="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="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_dialog_title" msgid="6688664582871779215">"Kai kurios programos geriausiai veikia stačiuoju režimu"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Pabandykite naudoti vieną iš šių parinkčių, kad išnaudotumėte visą vietą"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pasukite įrenginį, kad įjungtumėte viso ekrano režimą"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dukart palieskite šalia programos, kad pakeistumėte jos poziciją"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
index 52017dca2b94..04265ca01b48 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Uždaryti"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string>
- <string name="pip_move" msgid="158770205886688553">"Perkelti"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Išskleisti"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Sutraukti"</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="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 3599c14dcadd..a77160bc262a 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Pieskarieties, lai restartētu šo lietotni un uzlabotu attēlojumu."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Pieskarieties, lai restartētu šo lietotni un pārietu pilnekrāna režīmā."</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="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_dialog_title" msgid="6688664582871779215">"Dažas lietotnes vislabāk darbojas portreta režīmā"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Izmēģiniet vienu no šīm iespējām, lai efektīvi izmantotu pieejamo vietu"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pagrieziet ierīci, lai aktivizētu pilnekrāna režīmu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Veiciet dubultskārienu blakus lietotnei, lai manītu tās pozīciju"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
index 11abac6f6197..8c6191e00833 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Aizvērt"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string>
- <string name="pip_move" msgid="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_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="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 6c41b0ca0afd..bac0c9eee4c2 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Допрете за да ја рестартирате апликацијава и да ја отворите на цел екран."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</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_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
index 21293223b882..beef1fef862b 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Затвори"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</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_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="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 76dfc0d944a5..de0f837fcd3f 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"മികച്ച കാഴ്‌ചയ്‌ക്കായി ഈ ആപ്പ് റീസ്‌റ്റാർട്ട് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ഈ ആപ്പ് റീസ്‌റ്റാർട്ട് ചെയ്‌ത് പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ടാപ്പ് ചെയ്യുക."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ക്യാമറ പ്രശ്നങ്ങളുണ്ടോ?\nശരിയാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</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_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index 549e39b21101..c2a532d09647 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"അടയ്ക്കുക"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്‍ണ്ണ സ്ക്രീന്‍"</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_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="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 a8bd85ed27f5..1205306e0833 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Харагдах байдлыг сайжруулахын тулд энэ аппыг товшиж, дахин эхлүүлнэ үү."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Энэ аппыг дахин эхлүүлж, бүтэн дэлгэцэд орохын тулд товшино уу."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерын асуудал гарсан уу?\nДахин тааруулахын тулд товшино уу"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</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_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
index 9a85d96ca602..bf8c59b57359 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Хаах"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</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_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="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 5874812d9281..c91d06fdf3d5 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"अधिक चांगल्या व्ह्यूसाठी हे अ‍ॅप रीस्टार्ट करण्याकरिता टॅप करा."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"हे अ‍ॅप रीस्टार्ट करण्यासाठी आणि फुल स्क्रीन करण्यासाठी टॅप करा."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"कॅमेराशी संबंधित काही समस्या आहेत का?\nपुन्हा फिट करण्यासाठी टॅप करा"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्‍यासाठी टॅप करा."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</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_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index a9779b3a3e89..5d519b7afe9a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"बंद करा"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</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_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="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 88270df81660..652a9919d163 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Ketik untuk memulakan semula apl ini dan menggunakan skrin penuh."</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="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_dialog_title" msgid="6688664582871779215">"Sesetengah apl berfungsi paling baik dalam mod potret"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Cuba salah satu daripada pilihan ini untuk memanfaatkan ruang anda sepenuhnya"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar peranti anda untuk beralih ke skrin penuh"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketik dua kali bersebelahan apl untuk menempatkan semula apl"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
index 8fe992d9f3b9..08642c47c91a 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Tutup"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</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_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="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 1b24ca5c07fd..15d182c6451e 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -66,22 +66,19 @@
<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="6712141648865547958">"ပိုကောင်းသောမြင်ကွင်းအတွက် ဤအက်ပ်ပြန်စရန် တို့နိုင်သည်။"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ဤအက်ပ်ကို ပြန်စပြီး ဖန်သားပြင်အပြည့်လုပ်ရန် တို့ပါ။"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ကင်မရာပြဿနာလား။\nပြင်ဆင်ရန် တို့ပါ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
- <string name="letterbox_education_split_screen_text" msgid="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="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
- <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"အချို့အက်ပ်များသည် ဒေါင်လိုက်တွင် အကောင်းဆုံးလုပ်ဆောင်သည်"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"သင့်နေရာကို အကောင်းဆုံးအသုံးပြုနိုင်ရန် ဤရွေးစရာများထဲမှ တစ်ခုကို စမ်းကြည့်ပါ"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ဖန်သားပြင်အပြည့်လုပ်ရန် သင့်စက်ကို လှည့်နိုင်သည်"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"အက်ပ်နေရာပြန်ချရန် ၎င်းဘေးတွင် နှစ်ချက်တို့နိုင်သည်"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
index 105628d8149e..e01daee115ca 100644
--- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ပိတ်ရန်"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</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_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="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 1149c8f8215f..9fd42b2f129c 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -63,8 +63,8 @@
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller 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 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_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,16 +72,13 @@
<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="6712141648865547958">"Trykk for å starte denne appen på nytt for bedre visning."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Trykk for å starte denne appen på nytt og vise den i fullskjerm."</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="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_dialog_title" msgid="6688664582871779215">"Noen apper fungerer best i stående format"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Prøv et av disse alternativene for å få mest mulig ut av plassen din"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Roter enheten for å starte fullskjerm"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dobbelttrykk ved siden av en app for å flytte den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
index ca63518df7a5..65ed0b7f5bff 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Lukk"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</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_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="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 dc7d98d1f702..8dfec88cc29d 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"यो एप अझ राम्रो हेर्न मिल्ने बनाउनका लागि यसलाई रिस्टार्ट गर्न ट्याप गर्नुहोस्।"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"यो एप रिस्टार्ट गर्न ट्याप गर्नुहोस् र फुल स्क्रिन मोडमा जानुहोस्।"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्यामेरासम्बन्धी समस्या देखियो?\nसमस्या हल गर्न ट्याप गर्नुहोस्"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</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_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
index 7cbf9e294e7b..d33fed67efb6 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"बन्द गर्नुहोस्"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</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_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="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 5bae2a34100c..8468b04c66da 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tik om deze app opnieuw op te starten voor een betere weergave."</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="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="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_dialog_title" msgid="6688664582871779215">"Sommige apps werken het best in de staande stand"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Probeer een van deze opties om optimaal gebruik te maken van je ruimte"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai je apparaat om naar volledig scherm te schakelen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
index 2deaeddc4080..9763c5665ab2 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Sluiten"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</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_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="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 c5ef4f10ce39..a8d8448edf99 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ଏକ ଆହୁରି ଭଲ ଭ୍ୟୁ ପାଇଁ ଏହି ଆପ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ଏହି ଆପକୁ ରିଷ୍ଟାର୍ଟ କରି ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"କ୍ୟାମେରାରେ ସମସ୍ୟା ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</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_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
index 0c1d99e4ca71..e0344855bd1f 100644
--- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍"</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_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="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 f0262052513c..f99176cb682d 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ਅਤੇ ਪੂਰੀ ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਓ।"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</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_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
index a1edde738775..9c01ac3f3cc0 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ਬੰਦ ਕਰੋ"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</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_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="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 22c0c37002c9..f2147c04d335 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Kliknij, aby zrestartować aplikację i zyskać lepszą widoczność."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Kliknij, by uruchomić tę aplikację ponownie i przejść w tryb pełnoekranowy."</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="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_dialog_title" msgid="6688664582871779215">"Niektóre aplikacje działają najlepiej w orientacji pionowej"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Wypróbuj jedną z tych opcji, aby jak najlepiej wykorzystać miejsce"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Obróć urządzenie, aby przejść do pełnego ekranu"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kliknij dwukrotnie obok aplikacji, aby ją przenieść"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
index 2bb90addc241..b922e2d5a6ba 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Zamknij"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string>
- <string name="pip_move" msgid="158770205886688553">"Przenieś"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Rozwiń"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Zwiń"</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="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 0bbffb3182b5..2efc5543dd87 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</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="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_dialog_title" msgid="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</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>
</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 14d1c34fd3e8..cc4eb3c32c1f 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Fechar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</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_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="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 07dd4d73ebd3..c68a6934dead 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar esta app e ficar em ecrã inteiro."</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="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_dialog_title" msgid="6688664582871779215">"Algumas apps funcionam melhor no modo vertical"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Experimente uma destas opções para aproveitar ao máximo o seu espaço"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rode o dispositivo para ficar em ecrã inteiro"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes junto a uma app para a reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</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>
</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 1ada4508714a..c4ae78d89ba8 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Fechar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</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_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="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 0bbffb3182b5..2efc5543dd87 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Toque para reiniciar o app e atualizar a visualização."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Toque para reiniciar o app e usar tela cheia."</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="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_dialog_title" msgid="6688664582871779215">"Alguns apps funcionam melhor em modo retrato"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Tente uma destas opções para aproveitar seu espaço ao máximo"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
index 14d1c34fd3e8..cc4eb3c32c1f 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Fechar"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</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_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="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 0aae6a509bdb..804d34f980ff 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -17,20 +17,20 @@
<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"</string>
- <string name="pip_phone_expand" msgid="2579292903468287504">"Extinde"</string>
+ <string name="pip_phone_close" msgid="5783752637260411309">"Închideți"</string>
+ <string name="pip_phone_expand" msgid="2579292903468287504">"Extindeți"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Setări"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesează ecranul împărțit"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Accesați ecranul împărțit"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Meniu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> este în modul picture-in-picture"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"Dacă nu 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="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="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
@@ -47,41 +47,38 @@
<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="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, 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="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="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">"Adaugă înapoi în stivă"</string>
+ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Adăugați î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">"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="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="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
- <string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</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="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string>
- <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_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_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">"Gestionează"</string>
+ <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionați"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
- <string name="restart_button_description" msgid="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="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="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="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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index b5245ffbf0bc..86a30f49df15 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Închide"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string>
- <string name="pip_move" msgid="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="3672999496647508701">" Apasă de două ori "<annotation icon="home_icon">"butonul ecran 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>
+ <string name="pip_move" msgid="1544227837964635439">"Mutați fereastra PIP"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"Extindeți fereastra PIP"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"Restrângeți fereastra PIP"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Apăsați de două ori "<annotation icon="home_icon">"butonul ecran de pornire"</annotation>" pentru comenzi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f1ff000ebc4c..95bf1cf11435 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Нажмите, чтобы перезапустить приложение и настроить удобный для просмотра вид"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Нажмите, чтобы перезапустить приложение и перейти в полноэкранный режим."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблемы с камерой?\nНажмите, чтобы исправить."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</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_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
index e7f55ec1bc57..08623e1e69c5 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Закрыть"</string>
+ <string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</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_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="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 39bd260c2901..23dd65ad7b31 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"වඩා හොඳ දසුනක් ලබා ගැනීම සඳහා මෙම යෙදුම යළි ඇරඹීමට තට්ටු කරන්න."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"මෙම යෙදුම යළි ඇරඹීමට සහ පූර්ණ තිරයට යාමට තට්ටු කරන්න."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"කැමරා ගැටලුද?\nයළි සවි කිරීමට තට්ටු කරන්න"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්‍රතිවර්තනය කිරීමට තට්ටු කරන්න"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</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_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
index 5478ce5d3d40..fbb0ebba0623 100644
--- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"වසන්න"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</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_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="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 88592315a53c..a231cacefb20 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Ak chcete zlepšiť zobrazenie, klepnutím túto aplikáciu reštartujte."</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="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="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_dialog_title" msgid="6688664582871779215">"Niektoré aplikácie fungujú najlepšie v režime na výšku"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Vyskúšajte jednu z týchto možností a využívajte svoj priestor naplno"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zariadenia prejdete do režimu celej obrazovky"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedľa aplikácie zmeníte jej pozíciu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
index 1df43afca2da..81cb0eafc759 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Zavrieť"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
- <string name="pip_move" msgid="158770205886688553">"Presunúť"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Rozbaliť"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Zbaliť"</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="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 22fe0f8bc657..adeaae978eaa 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Če želite boljši prikaz, se dotaknite za vnovični zagon te aplikacije."</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="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="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_dialog_title" msgid="6688664582871779215">"Nekatere aplikacije najbolje delujejo v navpični postavitvi"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Poskusite eno od teh možnosti za čim boljši izkoristek prostora"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Če želite preklopiti v celozaslonski način, zasukajte napravo."</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvakrat se dotaknite ob aplikaciji, če jo želite prestaviti."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
index 88fc8325aa01..060aaa0ce647 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Zapri"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string>
- <string name="pip_move" msgid="158770205886688553">"Premakni"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Razširi"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Strni"</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="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 2a3671b85d0d..2839b4bae7e4 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Trokit për të rifilluar këtë aplikacion për një pamje më të mirë."</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="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="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_dialog_title" msgid="6688664582871779215">"Disa aplikacione funksionojnë më mirë në modalitetin vertikal"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Provo një nga këto opsione për ta shfrytëzuar sa më mirë hapësirën"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rrotullo ekranin për të kaluar në ekran të plotë"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Trokit dy herë pranë një aplikacioni për ta ripozicionuar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
index 58687e5867fe..9bfdb6a3edd8 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Mbyll"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string>
- <string name="pip_move" msgid="158770205886688553">"Lëviz"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Zgjero"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Palos"</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="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 09471037fde7..9db6b7c63610 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Додирните да бисте рестартовали ову апликацију ради бољег приказа."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Додирните да бисте рестартовали апликацију и прешли у режим целог екрана."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблема са камером?\nДодирните да бисте поново уклопили"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</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_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
index e850979174a3..6bc5c87bab48 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Затвори"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</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_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="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 70282a4b1a4d..f6bd55423cdc 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Tryck för att starta om appen och få en bättre vy."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Tryck för att starta om appen i helskärmsläge."</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="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_dialog_title" msgid="6688664582871779215">"Vissa appar fungerar bäst i stående läge"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Testa med ett av dessa alternativ för att få ut mest möjliga av ytan"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotera skärmen för att gå över till helskärmsläge"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryck snabbt två gånger bredvid en app för att flytta den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
index d3a9c3de66db..b3465ab1db85 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Stäng"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string>
- <string name="pip_move" msgid="158770205886688553">"Flytta"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Utöka"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Komprimera"</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="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 1aab85c54add..f6e558527ee5 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Gusa ili uzime na uwashe programu hii, kisha nenda kwenye skrini nzima."</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="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_dialog_title" msgid="6688664582871779215">"Baadhi ya programu hufanya kazi vizuri zaidi zikiwa wima"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Jaribu moja kati ya chaguo hizi ili utumie nafasi ya skrini yako kwa ufanisi"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungusha kifaa chako ili uende kwenye hali ya skrini nzima"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Gusa mara mbili karibu na programu ili uihamishe"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
index 7b9a310ff0b6..baff49ed821a 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Funga"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</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_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="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 befc8eb6f808..d8334adfe5ef 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கலாம், முழுத்திரையில் பார்க்கலாம்."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</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_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
index e201401e2e35..4439e299c919 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"மூடுக"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</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_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="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 a3e21f745707..733075550007 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -28,7 +28,7 @@
<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>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"మెరుగైన వీక్షణ కోసం ఈ యాప్‌ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేయండి."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"ఈ యాప్‌ను రీస్టార్ట్ చేయడానికి ట్యాప్ చేసి, ఆపై పూర్తి స్క్రీన్‌లోకి వెళ్లండి."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా సమస్యలు ఉన్నాయా?\nరీఫిట్ చేయడానికి ట్యాప్ చేయండి"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</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_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
index 6284d90cb11f..35579346615f 100644
--- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"మూసివేయండి"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్‌"</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_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="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-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 63d3bb83832d..cfee8ea3242e 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"แตะเพื่อรีสตาร์ทแอปนี้และรับมุมมองที่ดียิ่งขึ้น"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"แตะเพื่อรีสตาร์ทแอปนี้และแสดงแบบเต็มหน้าจอ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"หากพบปัญหากับกล้อง\nแตะเพื่อแก้ไข"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</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_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
index 27cf56c6e154..0a07d157ec6f 100644
--- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"ปิด"</string>
+ <string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</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_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="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 50334f5e772e..eed624dd5069 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"I-tap para i-restart ang app na ito para sa mas magandang view."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"I-tap para i-restart ang app na ito at mag-full screen."</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="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_dialog_title" msgid="6688664582871779215">"May ilang app na pinakamainam gamitin nang naka-portrait"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Subukan ang isa sa mga opsyong ito para masulit ang iyong space"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"I-rotate ang iyong device para mag-full screen"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Mag-double tap sa tabi ng isang app para iposisyon ito ulit"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
index 4cc050bebe5b..9a11a38fa492 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Isara"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <string name="pip_move" msgid="158770205886688553">"Ilipat"</string>
- <string name="pip_expand" msgid="1051966011679297308">"I-expand"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"I-collapse"</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="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 e1ea0df780e6..2b4a2d0550f0 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Bu uygulamayı yeniden başlatmak ve tam ekrana geçmek 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="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_dialog_title" msgid="6688664582871779215">"Bazı uygulamalar dikey modda en iyi performansı gösterir"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Alanınızı en verimli şekilde kullanmak için bu seçeneklerden birini deneyin"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana geçmek için cihazınızı döndürün"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Yeniden konumlandırmak için uygulamanın yanına iki kez dokunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
index 69bb608061e4..bf4bc6f1fff7 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Kapat"</string>
+ <string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
- <string name="pip_move" msgid="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="3672999496647508701">" Kontroller için "<annotation icon="home_icon">" ANA SAYFA "</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>
+ <string name="pip_move" msgid="1544227837964635439">"PIP\'yi taşı"</string>
+ <string name="pip_expand" msgid="7605396312689038178">"PIP penceresini genişlet"</string>
+ <string name="pip_collapse" msgid="5732233773786896094">"PIP penceresini daralt"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Kontroller için "<annotation icon="home_icon">" ANA SAYFA "</annotation>"\'ya iki kez basın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 9e713c77cacd..c3411a837c78 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Натисніть, щоб перезапустити цей додаток для зручнішого перегляду."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Натисніть, щоб перезапустити додаток і перейти в повноекранний режим."</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми з камерою?\nНатисніть, щоб пристосувати"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
+ <string name="letterbox_education_dialog_title" msgid="6688664582871779215">"Деякі додатки найкраще працюють у вертикальній орієнтації"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Щоб максимально ефективно використовувати місце на екрані, спробуйте виконати одну з наведених нижче дій"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Щоб перейти в повноекранний режим, поверніть пристрій"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Щоб перемістити додаток, двічі торкніться області поруч із ним"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
index 81a8285c58cf..7e9f54e68f54 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Закрити"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</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_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="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 596cf4b15d40..a31c2be25643 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"بہتر منظر کے لیے اس ایپ کو ری اسٹارٹ کرنے کی خاطر تھپتھپائیں۔"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"یہ ایپ دوبارہ شروع کرنے کے لیے تھپتھپائیں اور پوری اسکرین پر جائیں۔"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"کیمرے کے مسائل؟\nدوبارہ فٹ کرنے کیلئے تھپتھپائیں"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</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_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index e83885772f2d..c2ef69ff1488 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"بند کریں"</string>
+ <string name="pip_close" msgid="9135220303720555525">"‏PIP بند کریں"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</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_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="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 275940a22f25..2e3222560dde 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Bu ilovani qaytadan ishga tushirish va butun ekranda ochish 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="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_dialog_title" msgid="6688664582871779215">"Ayrim ilovalar tik holatda ishlashga eng mos"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Muhitdan yanada samarali foydalanish uchun quyidagilardan birini sinang"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Butun ekranda ochish uchun qurilmani buring"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Qayta joylash uchun keyingi ilova ustiga ikki marta bosing"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
index da953356628c..9ab95c80aa25 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Yopish"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string>
- <string name="pip_move" msgid="158770205886688553">"Boshqa joyga olish"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Yoyish"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Yopish"</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="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 24fb1ca57aec..8f3cffecc952 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</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="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="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_dialog_title" msgid="6688664582871779215">"Một số ứng dụng hoạt động tốt nhất ở chế độ dọc"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Hãy thử một trong các tuỳ chọn sau để tận dụng không gian"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xoay thiết bị để chuyển sang chế độ toàn màn hình"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Nhấn đúp vào bên cạnh ứng dụng để đặt lại vị trí"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
index 1f9260fdcff0..146376d3cab6 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Đóng"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string>
- <string name="pip_move" msgid="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_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="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 e52a7b556be0..19a9d371e435 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"点按即可重启此应用,获得更好的视图体验。"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"点按即可重启此应用并进入全屏模式。"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相机有问题?\n点按即可整修"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</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_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</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>
</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 399d639fe70f..55407d2c699d 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"关闭"</string>
+ <string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</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_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="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 eed1e5299365..0c40e963f2e4 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"輕按並重新啟動此應用程式,以取得更佳的觀看體驗。"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"輕按即可重新開啟此應用程式並放大至全螢幕。"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題?\n輕按即可修正"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</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_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</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>
</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 acbc26d033cd..15e278d8ecc2 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"關閉"</string>
+ <string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</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_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="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 f9f28ab02aa6..8691352cf94a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -72,16 +72,13 @@
<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="6712141648865547958">"請輕觸並重新啟動此應用程式,取得更良好的觀看體驗。"</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"輕觸即可重新啟動這個應用程式並進入全螢幕模式。"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題嗎?\n輕觸即可修正"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string>
- <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
- <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</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_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</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>
</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 f8c683ec3a60..0b17b31d23d0 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"關閉"</string>
+ <string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</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_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="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 8c1faa7895e7..44ffbc6afa45 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -19,7 +19,7 @@
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">"Amasethingi"</string>
+ <string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
@@ -72,16 +72,13 @@
<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="6712141648865547958">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono."</string>
+ <string name="restart_button_description" msgid="5887656107651190519">"Thepha ukuze uqale kabusha lolu hlelo lokusebenza uphinde uye kusikrini esigcwele."</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="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_dialog_title" msgid="6688664582871779215">"Amanye ama-app asebenza ngcono uma eme ngobude"</string>
+ <string name="letterbox_education_dialog_subtext" msgid="4853542518367719562">"Zama enye yalezi zinketho ukuze usebenzise isikhala sakho ngokugcwele"</string>
+ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungezisa idivayisi yakho ukuze uye esikrinini esigcwele"</string>
+ <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Thepha kabili eduze kwe-app ukuze uyimise kabusha"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
- <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</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>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
index 20243a9dfc9c..dad8c8128222 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
@@ -19,16 +19,10 @@
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="2955969519031223530">"Vala"</string>
+ <string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string>
- <string name="pip_move" msgid="158770205886688553">"Hambisa"</string>
- <string name="pip_expand" msgid="1051966011679297308">"Nweba"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Goqa"</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="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/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 30c3d50ed8ad..774f6c6379b2 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -23,6 +23,10 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
<bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+ <!-- Determines whether to register the shell transitions on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTransitionsOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
@@ -107,4 +111,14 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_appsSupportMultiInstancesSplit">
+ </string-array>
+
+ <!-- Whether the extended restart dialog is enabled -->
+ <bool name="config_letterboxIsRestartDialogEnabled">false</bool>
+
+ <!-- Whether the additional education about reachability is enabled -->
+ <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 0bc70857a113..3ee20ea95ee5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -321,4 +321,21 @@
<!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
<dimen name="floating_dismiss_circle_small">120dp</dimen>
+
+ <!-- The thickness of shadows of a window that has focus in DIP. -->
+ <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen>
+
+ <!-- The thickness of shadows of a window that doesn't have focus in DIP. -->
+ <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen>
+
+ <!-- Height of button (32dp) + 2 * margin (5dp each). -->
+ <dimen name="freeform_decor_caption_height">42dp</dimen>
+
+ <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
+ <dimen name="freeform_decor_caption_width">216dp</dimen>
+
+ <dimen name="freeform_resize_handle">30dp</dimen>
+
+ <dimen name="freeform_resize_corner">44dp</dimen>
+
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 1d1162daf249..25eddf834f3d 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -66,8 +66,10 @@
<!-- Multi-Window strings -->
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="dock_forced_resizable">App may not work with split-screen.</string>
- <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead. -->
+ <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead [CHAR LIMIT=NONE] -->
<string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
+ <!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
+ <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
@@ -193,4 +195,18 @@
<string name="minimize_button_text">Minimize</string>
<!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
<string name="close_button_text">Close</string>
+ <!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
+ <string name="back_button_text">Back</string>
+ <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
+ <string name="handle_text">Handle</string>
+ <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+ <string name="fullscreen_text">Fullscreen</string>
+ <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+ <string name="desktop_text">Desktop Mode</string>
+ <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+ <string name="split_screen_text">Split Screen</string>
+ <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+ <string name="more_button_text">More</string>
+ <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+ <string name="float_button_text">Float</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3ef4364..a8597210d72e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>
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 43679364b443..e58e785850fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -48,7 +46,6 @@ import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -567,6 +564,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Return list of {@link RunningTaskInfo}s for the given display.
+ *
+ * @return filtered list of tasks or empty list
+ */
+ public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+ ArrayList<RunningTaskInfo> result = new ArrayList<>();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ result.add(taskInfo);
+ }
+ }
+ return result;
+ }
+
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
@Nullable
public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -693,57 +706,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
- /**
- * Create a {@link WindowContainerTransaction} to clear task bounds.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have bounds cleared
- * @return {@link WindowContainerTransaction} with pending operations to clear bounds
- */
- public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- /**
- * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have windowing mode reset to {@link
- * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
- * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
- */
- public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- return wct;
- }
-
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
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 d76ad3d27c70..95a89b1d23ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,7 +41,6 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -123,7 +122,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
@@ -225,16 +224,6 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mObscuredTouchRegion = obscuredRegion;
}
- private void onLocationChanged(WindowContainerTransaction wct) {
- // Update based on the screen bounds
- getBoundsOnScreen(mTmpRect);
- getRootView().getBoundsOnScreen(mTmpRootRect);
- if (!mTmpRootRect.contains(mTmpRect)) {
- mTmpRect.offsetTo(0, 0);
- }
- wct.setBounds(mTaskToken, mTmpRect);
- }
-
/**
* Call when view position or size has changed. Do not call when animating.
*/
@@ -247,10 +236,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
WindowContainerTransaction wct = new WindowContainerTransaction();
- onLocationChanged(wct);
+ updateWindowBounds(wct);
mSyncQueue.queue(wct);
}
+ private void updateWindowBounds(WindowContainerTransaction wct) {
+ getBoundsOnScreen(mTmpRect);
+ wct.setBounds(mTaskToken, mTmpRect);
+ }
+
/**
* Release this container if it is initialized.
*/
@@ -574,7 +568,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
.apply();
// TODO: determine if this is really necessary or not
- onLocationChanged(wct);
+ updateWindowBounds(wct);
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
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 83335ac24799..07d501201105 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -87,6 +87,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
+ boolean isEnabled() {
+ return mTransitions.isRegistered();
+ }
+
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
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 591e3476ecd9..00b9fcede4ca 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
@@ -30,6 +30,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
+import com.android.wm.shell.transition.Transitions;
+
/**
* Wrapper to handle the ActivityEmbedding animation update in one
* {@link SurfaceControl.Transaction}.
@@ -50,6 +52,16 @@ class ActivityEmbeddingAnimationAdapter {
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
private final Rect mWholeAnimationBounds = new Rect();
+ /**
+ * Area in absolute coordinate that should represent all the content to show for this window.
+ * This should be the end bounds for opening window, and start bounds for closing window in case
+ * the window is resizing during the open/close transition.
+ */
+ @NonNull
+ private final Rect mContentBounds = new Rect();
+ /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+ @NonNull
+ private final Point mContentRelOffset = new Point();
@NonNull
final Transformation mTransformation = new Transformation();
@@ -80,6 +92,21 @@ class ActivityEmbeddingAnimationAdapter {
mChange = change;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
+ if (Transitions.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.
+ final Rect startBounds = change.getStartAbsBounds();
+ final Rect endBounds = change.getEndAbsBounds();
+ mContentBounds.set(startBounds);
+ mContentRelOffset.set(change.getEndRelOffset());
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ } else {
+ mContentBounds.set(change.getEndAbsBounds());
+ mContentRelOffset.set(change.getEndRelOffset());
+ }
}
/**
@@ -110,8 +137,7 @@ class ActivityEmbeddingAnimationAdapter {
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- final Point offset = mChange.getEndRelOffset();
- mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+ mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -119,8 +145,8 @@ class ActivityEmbeddingAnimationAdapter {
// positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mChange.getEndAbsBounds());
- cropRect.offset(positionX - offset.x, positionY - offset.y);
+ final Rect cropRect = new Rect(mContentBounds);
+ cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
// Store the current offset of the surface top left from (0,0) in absolute coordinate.
final int offsetX = cropRect.left;
@@ -130,6 +156,10 @@ class ActivityEmbeddingAnimationAdapter {
if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0);
+ } else if (mAnimation.hasExtension()) {
+ // Allow the surface to be shown in its original bounds in case we want to use edge
+ // extensions.
+ cropRect.union(mContentBounds);
}
// cropRect is in absolute coordinate, so we need to translate it to surface top left.
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 756d80204833..164d2f149931 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
@@ -17,10 +17,12 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import android.animation.Animator;
@@ -45,6 +47,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/** To run the ActivityEmbedding animations. */
class ActivityEmbeddingAnimationRunner {
@@ -65,10 +68,31 @@ class ActivityEmbeddingAnimationRunner {
void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
+ // There may be some surface change that we want to apply after the start transaction is
+ // applied to make sure the surface is ready.
+ final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+ new ArrayList<>();
final Animator animator = createAnimator(info, startTransaction, finishTransaction,
- () -> mController.onAnimationFinished(transition));
- startTransaction.apply();
- animator.start();
+ () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
+
+ // Start the animation.
+ if (!postStartTransactionCallbacks.isEmpty()) {
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ startTransaction.apply(true /* sync */);
+
+ // Run tasks that require startTransaction to already be applied
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ postStartTransactionCallback.accept(t);
+ }
+ t.apply();
+ animator.start();
+ } else {
+ startTransaction.apply();
+ animator.start();
+ }
}
/**
@@ -85,23 +109,34 @@ class ActivityEmbeddingAnimationRunner {
Animator createAnimator(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Runnable animationFinishCallback) {
- final List<ActivityEmbeddingAnimationAdapter> adapters =
- createAnimationAdapters(info, startTransaction, finishTransaction);
- long duration = 0;
- for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
- duration = Math.max(duration, adapter.getDurationHint());
- }
+ @NonNull Runnable animationFinishCallback,
+ @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
+ final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
+ startTransaction);
final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(duration);
- animator.addUpdateListener((anim) -> {
- // Update all adapters in the same transaction.
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ long duration = 0;
+ if (adapters.isEmpty()) {
+ // Jump cut
+ // No need to modify the animator, but to update the startTransaction with the changes'
+ // ending states.
+ prepareForJumpCut(info, startTransaction);
+ } else {
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+ postStartTransactionCallbacks, adapters);
+ addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
- adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ duration = Math.max(duration, adapter.getDurationHint());
}
- t.apply();
- });
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ }
+ animator.setDuration(duration);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@@ -131,8 +166,7 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -148,25 +182,23 @@ class ActivityEmbeddingAnimationRunner {
return createChangeAnimationAdapters(info, startTransaction);
}
if (Transitions.isClosingType(info.getType())) {
- return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
+ return createCloseAnimationAdapters(info);
}
- return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
+ return createOpenAnimationAdapters(info);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
- true /* isOpening */, mAnimationSpec::loadOpenAnimation);
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, true /* isOpening */,
+ mAnimationSpec::loadOpenAnimation);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
- return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
- false /* isOpening */, mAnimationSpec::loadCloseAnimation);
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, false /* isOpening */,
+ mAnimationSpec::loadCloseAnimation);
}
/**
@@ -175,8 +207,7 @@ class ActivityEmbeddingAnimationRunner {
*/
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
+ @NonNull TransitionInfo info, boolean isOpening,
@NonNull AnimationProvider animationProvider) {
// We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
@@ -200,8 +231,7 @@ class ActivityEmbeddingAnimationRunner {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, startTransaction, finishTransaction, animationProvider,
- openingWholeScreenBounds);
+ info, change, animationProvider, openingWholeScreenBounds);
if (isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -209,8 +239,7 @@ class ActivityEmbeddingAnimationRunner {
}
for (TransitionInfo.Change change : closingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
- info, change, startTransaction, finishTransaction, animationProvider,
- closingWholeScreenBounds);
+ info, change, animationProvider, closingWholeScreenBounds);
if (!isOpening) {
adapter.overrideLayer(offsetLayer++);
}
@@ -219,20 +248,51 @@ class ActivityEmbeddingAnimationRunner {
return adapters;
}
+ /** Adds edge extension to the surfaces that have such an animation property. */
+ private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
+ @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ final Animation animation = adapter.mAnimation;
+ if (!animation.hasExtension()) {
+ continue;
+ }
+ final TransitionInfo.Change change = adapter.mChange;
+ if (Transitions.isOpeningType(adapter.mChange.getMode())) {
+ // Need to screenshot after startTransaction is applied otherwise activity
+ // may not be visible or ready yet.
+ postStartTransactionCallbacks.add(
+ t -> edgeExtendWindow(change, animation, t, finishTransaction));
+ } else {
+ // Can screenshot now (before startTransaction is applied)
+ edgeExtendWindow(change, animation, startTransaction, finishTransaction);
+ }
+ }
+ }
+
+ /** Adds background color to the transition if any animation has such a property. */
+ private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
+ adapter.mAnimation, 0 /* defaultColor */);
+ if (backgroundColor != 0) {
+ // We only need to show one color.
+ addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
+ finishTransaction);
+ return;
+ }
+ }
+ }
+
@NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
- // We may want to show a background color for open/close transition.
- final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
- 0 /* defaultColor */);
- if (backgroundColor != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
- finishTransaction);
- }
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds);
}
@@ -240,6 +300,10 @@ class ActivityEmbeddingAnimationRunner {
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ if (shouldUseJumpCutForChangeTransition(info)) {
+ return new ArrayList<>();
+ }
+
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
@@ -251,6 +315,8 @@ class ActivityEmbeddingAnimationRunner {
// 3. Animate the TaskFragment using Activity Change info (start/end bounds).
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
+ Animation changeAnimation = null;
+ Rect parentBounds = new Rect();
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -273,8 +339,19 @@ class ActivityEmbeddingAnimationRunner {
}
}
+ // The TaskFragment may be enter/exit split, so we take the union of both as the parent
+ // size.
+ parentBounds.union(boundsAnimationChange.getStartAbsBounds());
+ parentBounds.union(boundsAnimationChange.getEndAbsBounds());
+
+ // There are two animations in the array. The first one is for the start leash
+ // (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
- boundsAnimationChange.getEndAbsBounds());
+ parentBounds);
+ // Keep track as we might need to add background color for the animation.
+ // Although there may be multiple change animation, record one of them is sufficient
+ // because the background color will be added to the root leash for the whole animation.
+ changeAnimation = animations[1];
// Create a screenshot based on change, but attach it to the top of the
// boundsAnimationChange.
@@ -293,6 +370,14 @@ class ActivityEmbeddingAnimationRunner {
animations[1], boundsAnimationChange));
}
+ if (parentBounds.isEmpty()) {
+ throw new IllegalStateException(
+ "There should be at least one changing window to play the change animation");
+ }
+
+ // If there is no corresponding open/close window with the change, we should show background
+ // color to cover the empty part of the screen.
+ boolean shouldShouldBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -301,17 +386,28 @@ class ActivityEmbeddingAnimationRunner {
}
final Animation animation;
- if (change.getParent() != null
- && handledChanges.contains(info.getChange(change.getParent()))) {
- // No-op if it will be covered by the changing parent window.
+ if ((change.getParent() != null
+ && handledChanges.contains(info.getChange(change.getParent())))
+ || change.getMode() == TRANSIT_CHANGE) {
+ // 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())) {
- animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
+ shouldShouldBackgroundColor = false;
} else {
- animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
+ shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
}
+
+ if (shouldShouldBackgroundColor && changeAnimation != null) {
+ // Change animation may leave part of the screen empty. Show background color to cover
+ // that.
+ changeAnimation.setShowBackdrop(true);
+ }
+
return adapters;
}
@@ -339,6 +435,74 @@ class ActivityEmbeddingAnimationRunner {
animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
}
+ /**
+ * Whether we should use jump cut for the change transition.
+ * This normally happens when opening a new secondary with the existing primary using a
+ * different split layout. This can be complicated, like from horizontal to vertical split with
+ * new split pairs.
+ * Uses a jump cut animation to simplify.
+ */
+ private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
+ // There can be reparenting of changing Activity to new open TaskFragment, so we need to
+ // exclude both in the first iteration.
+ final List<TransitionInfo.Change> changingChanges = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getMode() != TRANSIT_CHANGE
+ || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+ continue;
+ }
+ changingChanges.add(change);
+ final WindowContainerToken parentToken = change.getParent();
+ if (parentToken != null) {
+ // 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())) {
+ changingChanges.add(parentChange);
+ }
+ }
+ }
+ if (changingChanges.isEmpty()) {
+ // No changing target found.
+ return true;
+ }
+
+ // Check if the transition contains both opening and closing windows.
+ boolean hasOpeningWindow = false;
+ boolean hasClosingWindow = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (changingChanges.contains(change)) {
+ continue;
+ }
+ if (change.getParent() != null
+ && changingChanges.contains(info.getChange(change.getParent()))) {
+ // No-op if it will be covered by the changing parent window.
+ continue;
+ }
+ hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
+ hasClosingWindow |= Transitions.isClosingType(change.getMode());
+ }
+ return hasOpeningWindow && hasClosingWindow;
+ }
+
+ /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
+ private void prepareForJumpCut(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ startTransaction.setWindowCrop(leash,
+ change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
+ if (change.getMode() == TRANSIT_CLOSE) {
+ startTransaction.hide(leash);
+ } else {
+ startTransaction.show(leash);
+ startTransaction.setAlpha(leash, 1f);
+ }
+ }
+ }
+
/** To provide an {@link Animation} based on the transition infos. */
private interface AnimationProvider {
Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
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 eb6ac7615266..d10a6744b5f1 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
@@ -21,7 +21,6 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.Rect;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -80,15 +79,25 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is opening in a change transition. */
@NonNull
- Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+ Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect parentBounds) {
+ // Use end bounds for opening.
final Rect bounds = change.getEndAbsBounds();
- final Point offset = change.getEndRelOffset();
- // The window will be animated in from left or right depends on its position.
- final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -98,15 +107,25 @@ class ActivityEmbeddingAnimationSpec {
/** Animation for window that is closing in a change transition. */
@NonNull
- Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
- final Rect bounds = change.getEndAbsBounds();
- final Point offset = change.getEndRelOffset();
- // The window will be animated out to left or right depends on its position.
- final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+ Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect parentBounds) {
+ // Use start bounds for closing.
+ final Rect bounds = change.getStartAbsBounds();
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -158,7 +177,7 @@ class ActivityEmbeddingAnimationSpec {
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
@@ -181,15 +200,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_open_enter
: com.android.internal.R.anim.task_fragment_clear_top_open_exit);
} else {
+ // Use the same edge extension animation as regular activity open.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_open_enter
- : com.android.internal.R.anim.task_fragment_open_exit);
+ ? com.android.internal.R.anim.activity_open_enter
+ : com.android.internal.R.anim.activity_open_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are opening at the same time, the animation applied to each will be the same.
@@ -205,15 +224,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation;
- // TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_close_enter
: com.android.internal.R.anim.task_fragment_clear_top_close_exit);
} else {
+ // Use the same edge extension animation as regular activity close.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_close_enter
- : com.android.internal.R.anim.task_fragment_close_exit);
+ ? com.android.internal.R.anim.activity_close_enter
+ : com.android.internal.R.anim.activity_close_exit);
}
// Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are closing at the same time, the animation applied to each will be the same.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b534f4..8cbe44b15e42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@ import com.android.wm.shell.common.annotations.ExternalThread;
public interface BackAnimation {
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
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 33ecdd88fad3..cbcd9498fe55 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
@@ -21,6 +21,7 @@ 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;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,10 +58,12 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -72,13 +75,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private static final String TAG = "BackAnimationController";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
- private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
- "persist.wm.debug.predictive_back_progress_threshold";
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
- SETTING_VALUE_ON) != SETTING_VALUE_OFF;
- private static final int PROGRESS_THRESHOLD = SystemProperties
- .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ SETTING_VALUE_ON) == SETTING_VALUE_ON;
+ /** Flag for U animation features */
+ public static boolean IS_U_ANIMATION_ENABLED =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
+ SETTING_VALUE_OFF) == 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 =
@@ -105,12 +109,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
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 float mProgressThreshold;
private final Runnable mResetTransitionRunnable = () -> {
finishAnimation();
mTransitionInProgress = false;
@@ -121,7 +125,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private IBackNaviAnimationController mBackAnimationController;
private BackAnimationAdaptor mBackAnimationAdaptor;
- private boolean mWaitingAnimationStart;
private final TouchTracker mTouchTracker = new TouchTracker();
private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
@@ -144,44 +147,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
};
/**
- * Helper class to record the touch location for gesture start and latest.
- */
- private static class TouchTracker {
- /**
- * Location of the latest touch event
- */
- private float mLatestTouchX;
- private float mLatestTouchY;
- private int mSwipeEdge;
-
- /**
- * Location of the initial touch event of the back gesture.
- */
- private float mInitTouchX;
- private float mInitTouchY;
-
- void update(float touchX, float touchY, int swipeEdge) {
- mLatestTouchX = touchX;
- mLatestTouchY = touchY;
- mSwipeEdge = swipeEdge;
- }
-
- void setGestureStartLocation(float touchX, float touchY) {
- mInitTouchX = touchX;
- mInitTouchY = touchY;
- }
-
- int getDeltaFromGestureStart(float touchX) {
- return Math.round(touchX - mInitTouchX);
- }
-
- void reset() {
- mInitTouchX = 0;
- mInitTouchY = 0;
- }
- }
-
- /**
* Cache the temporary callback and trigger result if gesture was finish before received
* BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
*/
@@ -208,15 +173,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
boolean consumed = false;
if (mWaitingAnimation && mOnBackCallback != null) {
if (mTriggerBack) {
- final BackEvent backFinish = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackInvoked(mOnBackCallback);
} else {
- final BackEvent backFinish = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackCancelled(mOnBackCallback);
}
@@ -231,21 +192,25 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
public BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ this(shellInit, shellController, shellExecutor, backgroundHandler,
+ new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+ context, context.getContentResolver());
}
@VisibleForTesting
BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
+ mShellController = shellController;
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
@@ -255,8 +220,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
shellInit.addInitCallback(this::onInit, this);
}
+ @VisibleForTesting
+ void setEnableUAnimation(boolean enable) {
+ IS_U_ANIMATION_ENABLED = enable;
+ }
+
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ this::createExternalInterface, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -289,7 +261,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return mBackAnimation;
}
- private final BackAnimation mBackAnimation = new BackAnimationImpl();
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IBackAnimationImpl(this);
+ }
+
+ private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
public Context getContext() {
@@ -302,17 +278,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private class BackAnimationImpl implements BackAnimation {
- private IBackAnimationImpl mBackAnimation;
-
- @Override
- public IBackAnimation createExternalInterface() {
- if (mBackAnimation != null) {
- mBackAnimation.invalidate();
- }
- mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
- return mBackAnimation;
- }
-
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -331,7 +296,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private static class IBackAnimationImpl extends IBackAnimation.Stub
+ implements ExternalInterfaceBinder {
private BackAnimationController mController;
IBackAnimationImpl(BackAnimationController controller) {
@@ -356,7 +322,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
(controller) -> controller.onBackToLauncherAnimationFinished());
}
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
}
@@ -399,7 +366,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
- mTouchTracker.update(touchX, touchY, swipeEdge);
+ mTouchTracker.update(touchX, touchY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -409,7 +376,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
- onGestureStarted(touchX, touchY);
+ onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
onMove(touchX, touchY, swipeEdge);
@@ -423,14 +390,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private void onGestureStarted(float touchX, float touchY) {
+ private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
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();
}
- mTouchTracker.setGestureStartLocation(touchX, touchY);
+ mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
mBackGestureStarted = true;
try {
@@ -459,6 +426,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration());
}
+ targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
mTransaction.apply();
} else if (dispatchToLauncher) {
targetCallback = mBackToLauncherCallback;
@@ -469,7 +437,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
if (!USE_TRANSITION || !dispatchToLauncher) {
- dispatchOnBackStarted(targetCallback);
+ dispatchOnBackStarted(
+ targetCallback,
+ mTouchTracker.createStartEvent(
+ mBackNavigationInfo.getDepartingAnimationTarget()));
}
}
@@ -509,29 +480,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX);
- float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
- float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
- if (USE_TRANSITION) {
- if (mBackAnimationController != null && mAnimationTarget != null) {
- final BackEvent backEvent = new BackEvent(
- touchX, touchY, progress, swipeEdge, mAnimationTarget);
+ final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
- }
- } else {
+ } else if (mEnableAnimations.get()) {
int backType = mBackNavigationInfo.getType();
- RemoteAnimationTarget animationTarget =
- mBackNavigationInfo.getDepartingAnimationTarget();
-
- BackEvent backEvent = new BackEvent(
- touchX, touchY, progress, swipeEdge, animationTarget);
- IOnBackInvokedCallback targetCallback = null;
+ IOnBackInvokedCallback targetCallback;
if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback;
- } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
- || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
- // TODO(208427216) Run the actual animation
- } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ } else {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
dispatchOnBackProgressed(targetCallback, backEvent);
@@ -615,18 +572,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|| mBackNavigationInfo.getDepartingAnimationTarget() != null);
}
- private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
+ BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackStarted();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackStarted(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
}
- private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
@@ -637,29 +597,38 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
- callback.onBackCancelled();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackCancelled();
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
}
- private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
+ private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackProgressed(backEvent);
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackProgressed(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
+ private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+ return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback)
+ && mEnableAnimations.get();
+ }
+
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -668,10 +637,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
mTriggerBack = triggerBack;
+ mTouchTracker.setTriggerBack(triggerBack);
}
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
- mProgressThreshold = progressThreshold;
+ mTouchTracker.setProgressThreshold(progressThreshold);
mTriggerThreshold = triggerThreshold;
}
@@ -681,6 +651,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
+ mAnimationTarget = null;
mTriggerBack = false;
mShouldStartOnNextMoveEvent = false;
if (backNavigationInfo == null) {
@@ -757,17 +728,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
tx.apply();
}
}
- // TODO animation target should be passed at onBackStarted
- dispatchOnBackStarted(mBackToLauncherCallback);
- // TODO This is Workaround for LauncherBackAnimationController, there will need
- // to dispatch onBackProgressed twice(startBack & updateBackProgress) to
- // initialize the animation data, for now that would happen when onMove
- // called, but there will no expected animation if the down -> up gesture
- // happen very fast which ACTION_MOVE only happen once.
- final BackEvent backInit = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
- dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+ dispatchOnBackStarted(mBackToLauncherCallback,
+ mTouchTracker.createStartEvent(mAnimationTarget));
+ final BackEvent backInit = mTouchTracker.createProgressEvent();
if (!mCachingBackDispatcher.consume()) {
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 000000000000..1e0f9bc6322f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
new file mode 100644
index 000000000000..ccfac65d6342
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.os.SystemProperties;
+import android.view.RemoteAnimationTarget;
+import android.window.BackEvent;
+
+/**
+ * Helper class to record the touch location for gesture and generate back events.
+ */
+class TouchTracker {
+ private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
+ "persist.wm.debug.predictive_back_progress_threshold";
+ private static final int PROGRESS_THRESHOLD = SystemProperties
+ .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ private float mProgressThreshold;
+ /**
+ * Location of the latest touch event
+ */
+ private float mLatestTouchX;
+ private float mLatestTouchY;
+ private boolean mTriggerBack;
+
+ /**
+ * Location of the initial touch event of the back gesture.
+ */
+ private float mInitTouchX;
+ private float mInitTouchY;
+ private float mStartThresholdX;
+ private int mSwipeEdge;
+ private boolean mCancelled;
+
+ void update(float touchX, float touchY) {
+ /**
+ * If back was previously cancelled but the user has started swiping in the forward
+ * direction again, restart back.
+ */
+ if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
+ || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
+ mCancelled = false;
+ mStartThresholdX = touchX;
+ }
+ mLatestTouchX = touchX;
+ mLatestTouchY = touchY;
+ }
+
+ void setTriggerBack(boolean triggerBack) {
+ if (mTriggerBack != triggerBack && !triggerBack) {
+ mCancelled = true;
+ }
+ mTriggerBack = triggerBack;
+ }
+
+ void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
+ mInitTouchX = touchX;
+ mInitTouchY = touchY;
+ mSwipeEdge = swipeEdge;
+ mStartThresholdX = mInitTouchX;
+ }
+
+ void reset() {
+ mInitTouchX = 0;
+ mInitTouchY = 0;
+ mStartThresholdX = 0;
+ mCancelled = false;
+ mTriggerBack = false;
+ mSwipeEdge = BackEvent.EDGE_LEFT;
+ }
+
+ BackEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ }
+
+ BackEvent createProgressEvent() {
+ float progressThreshold = PROGRESS_THRESHOLD >= 0
+ ? PROGRESS_THRESHOLD : mProgressThreshold;
+ progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
+ float progress = 0;
+ // Progress is always 0 when back is cancelled and not restarted.
+ if (!mCancelled) {
+ // If back is committed, progress is the distance between the last and first touch
+ // point, divided by the max drag distance. Otherwise, it's the distance between
+ // the last touch point and the starting threshold, divided by max drag distance.
+ // The starting threshold is initially the first touch location, and updated to
+ // the location everytime back is restarted after being cancelled.
+ float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
+ float deltaX = Math.max(
+ mSwipeEdge == BackEvent.EDGE_LEFT
+ ? mLatestTouchX - startX
+ : startX - mLatestTouchX,
+ 0);
+ progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
+ }
+ return createProgressEvent(progress);
+ }
+
+ BackEvent createProgressEvent(float progress) {
+ return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ }
+
+ public void setProgressThreshold(float progressThreshold) {
+ mProgressThreshold = progressThreshold;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 922472a26113..09dc68a4ccea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -870,7 +870,8 @@ public class Bubble implements BubbleViewProvider {
pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
- pw.print(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
+ pw.print(" isClearable: "); pw.println(mIsClearable);
+ pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
mExpandedView.dump(pw);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
index d6803e8052c6..56b13b8dcd46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.bubbles;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -52,14 +51,15 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
}
Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1, BITMAP_GENERATION_MODE_WITH_SHADOW);
+ userBadgedAppIcon, 1, MODE_WITH_SHADOW);
return createIconBitmap(userBadgedBitmap);
}
private class CircularRingDrawable extends CircularAdaptiveIcon {
final int mImportantConversationColor;
- final Rect mTempBounds = new Rect();
+ final int mRingWidth;
+ final Rect mInnerBounds = new Rect();
final Drawable mDr;
@@ -68,6 +68,8 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
mDr = dr;
mImportantConversationColor = mContext.getResources().getColor(
R.color.important_conversation, null);
+ mRingWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width);
}
@Override
@@ -75,11 +77,10 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
int save = canvas.save();
canvas.clipPath(getIconMask());
canvas.drawColor(mImportantConversationColor);
- int ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width);
- mTempBounds.set(getBounds());
- mTempBounds.inset(ringStrokeWidth, ringStrokeWidth);
- mDr.setBounds(mTempBounds);
+ mInnerBounds.set(getBounds());
+ mInnerBounds.inset(mRingWidth, mRingWidth);
+ canvas.translate(mInnerBounds.left, mInnerBounds.top);
+ mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
mDr.draw(canvas);
canvas.restoreToCount(save);
}
@@ -106,7 +107,6 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
int save = canvas.save();
canvas.clipPath(getIconMask());
- canvas.drawColor(Color.BLACK);
Drawable d;
if ((d = getBackground()) != null) {
d.draw(canvas);
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 93413dbe7e5f..dd8afff0df2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -24,14 +24,11 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT;
@@ -59,10 +56,8 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -126,18 +121,6 @@ public class BubbleController implements ConfigurationChangeListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
- // TODO(b/173386799) keep in sync with Launcher3, not hooked up to anything
- public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated";
- public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened";
- public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible";
- public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition";
- public static final String EXTRA_TASKBAR_ICON_SIZE = "taskbarIconSize";
- public static final String EXTRA_TASKBAR_BUBBLE_XY = "taskbarBubbleXY";
- public static final String EXTRA_TASKBAR_SIZE = "taskbarSize";
- public static final String LEFT_POSITION = "Left";
- public static final String RIGHT_POSITION = "Right";
- public static final String BOTTOM_POSITION = "Bottom";
-
// Should match with PhoneWindowManager
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
@@ -167,6 +150,9 @@ public class BubbleController implements ConfigurationChangeListener {
private final ShellExecutor mBackgroundExecutor;
+ // Whether or not we should show bubbles pinned at the bottom of the screen.
+ private boolean mIsBubbleBarEnabled;
+
private BubbleLogger mLogger;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@@ -227,7 +213,6 @@ public class BubbleController implements ConfigurationChangeListener {
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
-
public BubbleController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
@@ -470,52 +455,6 @@ public class BubbleController implements ConfigurationChangeListener {
mBubbleData.setExpanded(true);
}
- /** Called when any taskbar state changes (e.g. visibility, position, sizes). */
- private void onTaskbarChanged(Bundle b) {
- if (b == null) {
- return;
- }
- boolean isVisible = b.getBoolean(EXTRA_TASKBAR_VISIBLE, false /* default */);
- String position = b.getString(EXTRA_TASKBAR_POSITION, RIGHT_POSITION /* default */);
- @BubblePositioner.TaskbarPosition int taskbarPosition = TASKBAR_POSITION_NONE;
- switch (position) {
- case LEFT_POSITION:
- taskbarPosition = TASKBAR_POSITION_LEFT;
- break;
- case RIGHT_POSITION:
- taskbarPosition = TASKBAR_POSITION_RIGHT;
- break;
- case BOTTOM_POSITION:
- taskbarPosition = TASKBAR_POSITION_BOTTOM;
- break;
- }
- int[] itemPosition = b.getIntArray(EXTRA_TASKBAR_BUBBLE_XY);
- int iconSize = b.getInt(EXTRA_TASKBAR_ICON_SIZE);
- int taskbarSize = b.getInt(EXTRA_TASKBAR_SIZE);
- Log.w(TAG, "onTaskbarChanged:"
- + " isVisible: " + isVisible
- + " position: " + position
- + " itemPosition: " + itemPosition[0] + "," + itemPosition[1]
- + " iconSize: " + iconSize);
- PointF point = new PointF(itemPosition[0], itemPosition[1]);
- mBubblePositioner.setPinnedLocation(isVisible ? point : null);
- mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize);
- if (mStackView != null) {
- if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) {
- // If taskbar was created, add and remove the window so that bubbles display on top
- removeFromWindowManagerMaybe();
- addToWindowManagerMaybe();
- }
- mStackView.updateStackPosition();
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
- mStackView.onDisplaySizeChanged();
- }
- if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) {
- openBubbleOverflow();
- }
- }
-
/**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
@@ -536,8 +475,13 @@ public class BubbleController implements ConfigurationChangeListener {
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
+ boolean didChange = mIsStatusBarShade != isShade;
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
+ }
mIsStatusBarShade = isShade;
- if (!mIsStatusBarShade) {
+ if (!mIsStatusBarShade && didChange) {
+ // Only collapse stack on change
collapseStack();
}
@@ -589,6 +533,12 @@ public class BubbleController implements ConfigurationChangeListener {
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
+ /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
+ public void setBubbleBarEnabled(boolean enabled) {
+ mIsBubbleBarEnabled = enabled;
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -655,6 +605,12 @@ public class BubbleController implements ConfigurationChangeListener {
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
+ if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
+ mBubblePositioner.setUsePinnedLocation(true);
+ } else {
+ mBubblePositioner.setUsePinnedLocation(false);
+ }
+
addToWindowManagerMaybe();
}
@@ -1017,14 +973,18 @@ public class BubbleController implements ConfigurationChangeListener {
}
/**
- * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
- * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
- * is supported at a time.
+ * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
+ * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
+ * bubble is supported at a time.
*
* @param intent the intent to display in the bubble expanded view.
*/
- public void addAppBubble(Intent intent) {
+ public void showAppBubble(Intent intent) {
if (intent == null || intent.getPackage() == null) return;
+
+ PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+ if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
+
Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
@@ -1547,18 +1507,23 @@ public class BubbleController implements ConfigurationChangeListener {
}
PackageManager packageManager = getPackageManagerForUser(
context, entry.getStatusBarNotification().getUser().getIdentifier());
- ActivityInfo info =
- intent.getIntent().resolveActivityInfo(packageManager, 0);
+ return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
+ }
+
+ static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
+ if (intent == null) {
+ Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
+ return false;
+ }
+ ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
if (info == null) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " couldn't find activity info for intent: "
- + intent);
+ Log.w(TAG, "Unable to send as bubble: " + key
+ + " couldn't find activity info for intent: " + intent);
return false;
}
if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " activity is not resizable for intent: "
- + intent);
+ Log.w(TAG, "Unable to send as bubble: " + key
+ + " activity is not resizable for intent: " + intent);
return false;
}
return true;
@@ -1732,9 +1697,9 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void onTaskbarChanged(Bundle b) {
+ public void showAppBubble(Intent intent) {
mMainExecutor.execute(() -> {
- BubbleController.this.onTaskbarChanged(b);
+ BubbleController.this.showAppBubble(intent);
});
}
@@ -1849,6 +1814,13 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
+ public void setBubbleBarEnabled(boolean enabled) {
+ mMainExecutor.execute(() -> {
+ BubbleController.this.setBubbleBarEnabled(enabled);
+ });
+ }
+
+ @Override
public void onNotificationPanelExpandedChanged(boolean expanded) {
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 5dab8a071f76..4ded3ea951e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -79,6 +79,6 @@ public class BubbleIconFactory extends BaseIconFactory {
true /* shrinkNonAdaptiveIcons */,
null /* outscale */,
outScale);
- return createIconBitmap(icon, outScale[0], BITMAP_GENERATION_MODE_WITH_SHADOW);
+ return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index dbad5df9cf56..07c58527a815 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -713,6 +713,9 @@ public class BubblePositioner {
* is being shown.
*/
public PointF getDefaultStartPosition() {
+ if (mPinLocation != null) {
+ return mPinLocation;
+ }
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
@@ -766,11 +769,18 @@ public class BubblePositioner {
}
/**
- * In some situations bubbles will be pinned to a specific onscreen location. This sets the
- * location to anchor the stack to.
+ * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
+ * bubbles should be pinned or not.
*/
- public void setPinnedLocation(PointF point) {
- mPinLocation = point;
+ public void setUsePinnedLocation(boolean usePinnedLocation) {
+ if (usePinnedLocation) {
+ mShowingInTaskbar = true;
+ mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
+ mPositionRect.bottom - mBubbleSize);
+ } else {
+ mPinLocation = null;
+ mShowingInTaskbar = false;
+ }
}
/**
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 be100bb1dd34..f2afefe243bc 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
@@ -83,7 +83,6 @@ import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
-import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -105,11 +104,6 @@ import java.util.stream.Collectors;
*/
public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
- /**
- * Set to {@code true} to enable home gesture handling in bubbles
- */
- public static final boolean HOME_GESTURE_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
@@ -613,16 +607,11 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- if (mPositioner.showingInTaskbar()) {
- // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
- mMagnetizedObject = null;
- } else {
- // Save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
- mMagnetizedObject.clearAllTargets();
- mMagnetizedObject.addTarget(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- }
+ // Save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+ mMagnetizedObject.clearAllTargets();
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
mIsDraggingStack = true;
@@ -641,10 +630,7 @@ public class BubbleStackView extends FrameLayout
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
// If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating
- // Also ignore events if we shouldn't be draggable.
- || (mPositioner.showingInTaskbar() && !mIsExpanded)
- || mShowedUserEducationInTouchListenerActive) {
+ if (mIsExpansionAnimating || mShowedUserEducationInTouchListenerActive) {
return;
}
@@ -661,7 +647,7 @@ public class BubbleStackView extends FrameLayout
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
updateBubbleShadows(true /* showForAllBubbles */);
- if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
+ if (mBubbleData.isExpanded()) {
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
@@ -678,9 +664,7 @@ public class BubbleStackView extends FrameLayout
public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy, float velX, float velY) {
// If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating
- // Also ignore events if we shouldn't be draggable.
- || (mPositioner.showingInTaskbar() && !mIsExpanded)) {
+ if (mIsExpansionAnimating) {
return;
}
if (mShowedUserEducationInTouchListenerActive) {
@@ -696,6 +680,8 @@ public class BubbleStackView extends FrameLayout
// Re-show the expanded view if we hid it.
showExpandedViewIfNeeded();
+ } else if (mPositioner.showingInTaskbar()) {
+ mStackAnimationController.snapStackBack();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
@@ -906,12 +892,8 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
onBubbleAnimatedOut, this);
- if (HOME_GESTURE_ENABLED) {
- mExpandedViewAnimationController =
- new ExpandedViewAnimationControllerImpl(context, mPositioner);
- } else {
- mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
- }
+ mExpandedViewAnimationController =
+ new ExpandedViewAnimationControllerImpl(context, mPositioner);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
@@ -1973,11 +1955,8 @@ public class BubbleStackView extends FrameLayout
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
- if (HOME_GESTURE_ENABLED) {
- animateCollapse();
- } else {
- animateCollapseWithScale();
- }
+ animateCollapse();
+ showManageMenu(false);
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
@@ -1985,13 +1964,11 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- if (HOME_GESTURE_ENABLED) {
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
- if (!notifPanelExpanded && mIsExpanded) {
- startMonitoringSwipeUpGesture();
- }
- });
- }
+ mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ if (!notifPanelExpanded && mIsExpanded) {
+ startMonitoringSwipeUpGesture();
+ }
+ });
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
@@ -2307,106 +2284,6 @@ public class BubbleStackView extends FrameLayout
mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
}
- private void animateCollapseWithScale() {
- cancelDelayedExpandCollapseSwitchAnimations();
-
- if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
- mManageEduView.hide();
- }
- // Hide the menu if it's visible.
- showManageMenu(false);
-
- mIsExpanded = false;
- mIsExpansionAnimating = true;
-
- showScrim(false);
-
- mBubbleContainer.cancelAllAnimations();
-
- // If we were in the middle of swapping, the animating-out surface would have been scaling
- // to zero - finish it off.
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
- mAnimatingOutSurfaceContainer.setScaleX(0f);
- mAnimatingOutSurfaceContainer.setScaleY(0f);
-
- // Let the expanded animation controller know that it shouldn't animate child adds/reorders
- // since we're about to animate collapsed.
- mExpandedAnimationController.notifyPreparingToCollapse();
-
- mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
- /* collapseTo */,
- () -> mBubbleContainer.setActiveController(mStackAnimationController));
-
- int index;
- if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
- index = mBubbleData.getBubbles().size();
- } else {
- index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
- }
- // Value the bubble is animating from (back into the stack).
- final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
- if (mPositioner.showBubblesVertically()) {
- float pivotX;
- float pivotY = p.y + mBubbleSize / 2f;
- if (mStackOnLeftOrWillBe) {
- pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
- } else {
- pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
- }
- mExpandedViewContainerMatrix.setScale(
- 1f, 1f,
- pivotX, pivotY);
- } else {
- mExpandedViewContainerMatrix.setScale(
- 1f, 1f,
- p.x + mBubbleSize / 2f,
- p.y + mBubbleSize + mExpandedViewPadding);
- }
-
- mExpandedViewAlphaAnimator.reverse();
-
- // When the animation completes, we should no longer be showing the content.
- if (mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setContentVisibility(false);
- }
-
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
- mScaleOutSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
- mScaleOutSpringConfig)
- .addUpdateListener((target, values) -> {
- mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- })
- .withEndActions(() -> {
- final BubbleViewProvider previouslySelected = mExpandedBubble;
- beforeExpandedViewAnimation();
- if (mManageEduView != null) {
- mManageEduView.hide();
- }
-
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
- updateOverflowVisibility();
- updateZOrder();
- updateBadges(true /* setBadgeForCollapsedStack */);
- afterExpandedViewAnimation();
- if (previouslySelected != null) {
- previouslySelected.setTaskViewVisibility(false);
- }
- })
- .start();
- }
-
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
@@ -2588,65 +2465,6 @@ public class BubbleStackView extends FrameLayout
* and clip the expanded view.
*/
public void setImeVisible(boolean visible) {
- if (HOME_GESTURE_ENABLED) {
- setImeVisibleInternal(visible);
- } else {
- setImeVisibleWithoutClipping(visible);
- }
- }
-
- private void setImeVisibleWithoutClipping(boolean visible) {
- if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
- // This will update the animation so the bubbles move to position for the IME
- mExpandedAnimationController.expandFromStack(() -> {
- updatePointerPosition(false /* forIme */);
- afterExpandedViewAnimation();
- } /* after */);
- return;
- }
-
- if (!mIsExpanded && getBubbleCount() > 0) {
- final float stackDestinationY =
- mStackAnimationController.animateForImeVisibility(visible);
-
- // How far the stack is animating due to IME, we'll just animate the flyout by that
- // much too.
- final float stackDy =
- stackDestinationY - mStackAnimationController.getStackPosition().y;
-
- // If the flyout is visible, translate it along with the bubble stack.
- if (mFlyout.getVisibility() == VISIBLE) {
- PhysicsAnimator.getInstance(mFlyout)
- .spring(DynamicAnimation.TRANSLATION_Y,
- mFlyout.getTranslationY() + stackDy,
- FLYOUT_IME_ANIMATION_SPRING_CONFIG)
- .start();
- }
- } else if (mPositioner.showBubblesVertically() && mIsExpanded
- && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
- getState()).y;
- float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
- mExpandedBubble.getExpandedView().setImeVisible(visible);
- if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
- mExpandedViewContainer.animate().translationY(newExpandedViewTop);
- }
-
- List<Animator> animList = new ArrayList();
- for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
- View child = mBubbleContainer.getChildAt(i);
- float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
- ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
- animList.add(anim);
- }
- updatePointerPosition(true /* forIme */);
- AnimatorSet set = new AnimatorSet();
- set.playTogether(animList);
- set.start();
- }
- }
-
- private void setImeVisibleInternal(boolean visible) {
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
// This will update the animation so the bubbles move to position for the IME
mExpandedAnimationController.expandFromStack(() -> {
@@ -3105,8 +2923,10 @@ public class BubbleStackView extends FrameLayout
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
- // Update the AV's obscured touchable region for the new visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // Update the AV's obscured touchable region for the new state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
})
.start();
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 b3104b518440..465d1abe0a3d 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
@@ -22,8 +22,8 @@ import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
+import android.content.Intent;
import android.content.pm.UserInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -109,14 +109,20 @@ public interface Bubbles {
void expandStackAndSelectBubble(Bubble bubble);
/**
+ * Adds and expands bubble that is not notification based, but instead based on an intent from
+ * the app. The intent must be explicit (i.e. include a package name or fully qualified
+ * component class name) and the activity for it should be resizable.
+ *
+ * @param intent the intent to populate the bubble.
+ */
+ void showAppBubble(Intent intent);
+
+ /**
* @return a bubble that matches the provided shortcutId, if one exists.
*/
@Nullable
Bubble getBubbleWithShortcutId(String shortcutId);
- /** Called for any taskbar changes. */
- void onTaskbarChanged(Bundle b);
-
/**
* We intercept notification entries (including group summaries) dismissed by the user when
* there is an active bubble associated with it. We do this so that developers can still
@@ -236,6 +242,11 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
+ /**
+ * Sets whether bubble bar should be enabled or not.
+ */
+ void setBubbleBarEnabled(boolean enabled);
+
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index b91062f891e8..33629f9f4622 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -20,7 +20,6 @@ import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
import android.content.res.Resources;
import android.graphics.Path;
@@ -81,11 +80,6 @@ public class ExpandedAnimationController
new PhysicsAnimator.SpringConfig(
EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
- private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
- new PhysicsAnimator.SpringConfig(
- EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
- SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Size of each bubble. */
@@ -307,14 +301,8 @@ public class ExpandedAnimationController
(firstBubbleLeads && index == 0)
|| (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
- Interpolator interpolator;
- if (HOME_GESTURE_ENABLED) {
- // When home gesture is enabled, we use a different animation timing for collapse
- interpolator = expanding
- ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
- } else {
- interpolator = Interpolators.LINEAR;
- }
+ Interpolator interpolator = expanding
+ ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
animation
.followAnimatedTargetAlongPath(
@@ -564,16 +552,10 @@ public class ExpandedAnimationController
finishRemoval.run();
mOnBubbleAnimatedOutAction.run();
} else {
- PhysicsAnimator.SpringConfig springConfig;
- if (HOME_GESTURE_ENABLED) {
- springConfig = mAnimateOutSpringConfig;
- } else {
- springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
- }
PhysicsAnimator.getInstance(child)
.spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.SCALE_X, 0f, springConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
+ .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
.withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
deleted file mode 100644
index bb8a3aaaf551..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles.animation;
-
-import com.android.wm.shell.bubbles.BubbleExpandedView;
-
-/**
- * Stub implementation {@link ExpandedViewAnimationController} that does not animate the
- * {@link BubbleExpandedView}
- */
-public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
- @Override
- public void setExpandedView(BubbleExpandedView expandedView) {
- }
-
- @Override
- public void updateDrag(float distance) {
- }
-
- @Override
- public void setSwipeVelocity(float velocity) {
- }
-
- @Override
- public boolean shouldCollapse() {
- return false;
- }
-
- @Override
- public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- }
-
- @Override
- public void animateBackToExpanded() {
- }
-
- @Override
- public void animateForImeVisibilityChange(boolean visible) {
- }
-
- @Override
- public void reset() {
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 961722ba9bc0..0ee0ea60a1bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -417,6 +417,17 @@ public class StackAnimationController extends
}
/**
+ * Snaps the stack back to the previous resting position.
+ */
+ public void snapStackBack() {
+ if (mLayout == null) {
+ return;
+ }
+ PointF p = getStackPositionAlongNearestHorizontalEdge();
+ springStackAfterFling(p.x, p.y);
+ }
+
+ /**
* Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
*/
public PointF getStackPositionAlongNearestHorizontalEdge() {
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 266cf294a950..7f7af935ff2b 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
@@ -274,29 +274,30 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (hasImeSourceControl) {
- final Point lastSurfacePosition = mImeSourceControl != null
- ? mImeSourceControl.getSurfacePosition() : null;
- final boolean positionChanged =
- !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
- final boolean leashChanged =
- !haveSameLeash(mImeSourceControl, imeSourceControl);
if (mAnimation != null) {
+ final Point lastSurfacePosition = hadImeSourceControl
+ ? mImeSourceControl.getSurfacePosition() : null;
+ final boolean positionChanged =
+ !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
startAnimation(mImeShowing, true /* forceRestart */);
}
} else {
- if (leashChanged) {
+ if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
applyVisibilityToLeash(imeSourceControl);
}
if (!mImeShowing) {
removeImeSurface();
}
- if (mImeSourceControl != null) {
- mImeSourceControl.release(SurfaceControl::release);
- }
}
- mImeSourceControl = imeSourceControl;
+ } else if (mAnimation != null) {
+ mAnimation.cancel();
+ }
+
+ if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
+ mImeSourceControl.release(SurfaceControl::release);
}
+ mImeSourceControl = imeSourceControl;
}
private void applyVisibilityToLeash(InsetsSourceControl imeSourceControl) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
new file mode 100644
index 000000000000..e029358cb3a2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
@@ -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.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.content.Intent.EXTRA_DOCK_STATE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Provides information about the docked state of the device.
+ */
+@WMSingleton
+public class DockStateReader {
+
+ private static final IntentFilter DOCK_INTENT_FILTER = new IntentFilter(
+ Intent.ACTION_DOCK_EVENT);
+
+ private final Context mContext;
+
+ @Inject
+ public DockStateReader(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * @return True if the device is docked and false otherwise.
+ */
+ public boolean isDocked() {
+ Intent dockStatus = mContext.registerReceiver(/* receiver */ null, DOCK_INTENT_FILTER);
+ if (dockStatus != null) {
+ int dockState = dockStatus.getIntExtra(EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ return dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
index f79ca1039865..aa5b0cb628e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-package com.android.wm.shell.floating;
+package com.android.wm.shell.common;
-import android.content.Intent;
+import android.os.IBinder;
/**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * An interface for binders which can be registered to be sent to other processes.
*/
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
+public interface ExternalInterfaceBinder {
+ /**
+ * Invalidates this binder (detaches it from the controller it would call).
+ */
+ void invalidate();
+ /**
+ * Returns the IBinder to send.
+ */
+ IBinder asBinder();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java
index b77ac8a2b951..e46ee28b3ddb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java
@@ -29,6 +29,9 @@ import java.util.function.Consumer;
* Manages the lifecycle of a single instance of a remote listener, including the clean up if the
* remote process dies. All calls on this class should happen on the main shell thread.
*
+ * Any external interface using this listener should also unregister the listener when it is
+ * invalidated, otherwise it may leak binder death recipients.
+ *
* @param <C> The controller (must be RemoteCallable)
* @param <L> The remote listener interface type
*/
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 8bc16bcc9d9d..c63419851f7f 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
@@ -46,8 +46,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* Divider for multi window splits.
@@ -74,6 +76,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
private boolean mSetTouchRegion = true;
+ private int mLastDraggingPosition;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -216,7 +219,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
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.
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ // 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));
}
@@ -296,6 +303,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
if (mMoving) {
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ mLastDraggingPosition = position;
mSplitLayout.updateDivideBounds(position);
}
break;
@@ -364,9 +372,21 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mViewHost.relayout(lp);
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (interactive == mInteractive) return;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
+ from);
mInteractive = interactive;
+ if (!mInteractive && mMoving) {
+ final int position = mSplitLayout.getDividePosition();
+ mSplitLayout.flingDividePosition(
+ mLastDraggingPosition,
+ position,
+ mSplitLayout.FLING_RESIZE_DURATION,
+ () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+ mMoving = false;
+ }
releaseTouching();
mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
}
@@ -383,5 +403,10 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
return true;
}
+
+ @Override
+ public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+ return true;
+ }
}
}
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 74f8bf9ac863..a9d3c9f154cd 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
@@ -48,10 +48,9 @@ import androidx.annotation.NonNull;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
-import java.util.function.Consumer;
-
/**
* Handles split decor like showing resizing hint for a specific split.
*/
@@ -71,13 +70,19 @@ public class SplitDecorManager extends WindowlessWindowManager {
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
private SurfaceControl mGapBackgroundLeash;
+ private SurfaceControl mScreenshot;
private boolean mShown;
private boolean mIsResizing;
- private Rect mBounds = new Rect();
+ private final Rect mOldBounds = new Rect();
+ private final Rect mResizingBounds = new Rect();
+ private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
private int mIconSize;
+ private int mOffsetX;
+ private int mOffsetY;
+ private int mRunningAnimationCount = 0;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
@@ -154,24 +159,30 @@ public class SplitDecorManager extends WindowlessWindowManager {
mResizingIconView = null;
mIsResizing = false;
mShown = false;
+ mOldBounds.setEmpty();
+ mResizingBounds.setEmpty();
}
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
if (!mIsResizing) {
mIsResizing = true;
- mBounds.set(newBounds);
+ mOldBounds.set(newBounds);
}
+ mResizingBounds.set(newBounds);
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
final boolean show =
- newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
- final boolean animate = show != mShown;
- if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height();
+ final boolean update = show != mShown;
+ if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
@@ -184,10 +195,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
- if (mGapBackgroundLeash == null) {
+ if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
- final int left = isLandscape ? mBounds.width() : 0;
- final int top = isLandscape ? 0 : mBounds.height();
+ final int left = isLandscape ? mOldBounds.width() : 0;
+ final int top = isLandscape ? 0 : mOldBounds.height();
mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
// Fill up another side bounds area.
@@ -213,19 +224,60 @@ public class SplitDecorManager extends WindowlessWindowManager {
newBounds.width() / 2 - mIconSize / 2,
newBounds.height() / 2 - mIconSize / 2);
- if (animate) {
- startFadeAnimation(show, null /* finishedConsumer */);
+ if (update) {
+ if (immediately) {
+ t.setVisibility(mBackgroundLeash, show);
+ t.setVisibility(mIconLeash, show);
+ } else {
+ startFadeAnimation(show, false, null);
+ }
mShown = show;
}
}
/** Stops showing resizing hint. */
- public void onResized(SurfaceControl.Transaction t) {
+ public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+ if (mScreenshot != null) {
+ t.setPosition(mScreenshot, mOffsetX, mOffsetY);
+
+ final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+ final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+ va.addUpdateListener(valueAnimator -> {
+ final float progress = (float) valueAnimator.getAnimatedValue();
+ animT.setAlpha(mScreenshot, progress);
+ animT.apply();
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mRunningAnimationCount++;
+ }
+
+ @Override
+ public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+ mRunningAnimationCount--;
+ animT.remove(mScreenshot);
+ animT.apply();
+ animT.close();
+ mScreenshot = null;
+
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
+ }
+ });
+ va.start();
+ }
+
if (mResizingIconView == null) {
return;
}
mIsResizing = false;
+ mOffsetX = 0;
+ mOffsetY = 0;
+ mOldBounds.setEmpty();
+ mResizingBounds.setEmpty();
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
if (!mShown) {
// If fade-out animation is running, just add release callback to it.
@@ -245,10 +297,34 @@ public class SplitDecorManager extends WindowlessWindowManager {
mFadeAnimator.cancel();
}
if (mShown) {
- fadeOutDecor(null /* finishedCallback */);
+ fadeOutDecor(animFinishedCallback);
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
+ }
+ }
+
+ /** Screenshot host leash and attach on it if meet some conditions */
+ public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ mTempRect.set(mOldBounds);
+ mTempRect.offsetTo(0, 0);
+ mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+ Integer.MAX_VALUE - 1);
+ }
+ }
+
+ /** Set screenshot and attach on host leash it if meet some conditions */
+ public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+ if (screenshot == null || !screenshot.isValid()) return;
+
+ if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ mScreenshot = screenshot;
+ t.reparent(screenshot, mHostLeash);
+ t.setLayer(screenshot, Integer.MAX_VALUE - 1);
}
}
@@ -256,18 +332,15 @@ public class SplitDecorManager extends WindowlessWindowManager {
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
- startFadeAnimation(false /* show */, transaction -> {
- releaseDecor(transaction);
- if (finishedCallback != null) finishedCallback.run();
- });
+ startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
if (finishedCallback != null) finishedCallback.run();
}
}
- private void startFadeAnimation(boolean show,
- Consumer<SurfaceControl.Transaction> finishedConsumer) {
+ private void startFadeAnimation(boolean show, boolean releaseSurface,
+ Runnable finishedCallback) {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
mFadeAnimator.setDuration(FADE_DURATION);
@@ -284,15 +357,19 @@ public class SplitDecorManager extends WindowlessWindowManager {
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
+ mRunningAnimationCount++;
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
- } else {
- animT.hide(mGapBackgroundLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash);
+ }
+ if (mGapBackgroundLeash != null) {
+ animT.setVisibility(mGapBackgroundLeash, show);
}
+ animT.apply();
}
@Override
public void onAnimationEnd(@NonNull Animator animation) {
+ mRunningAnimationCount--;
if (!show) {
if (mBackgroundLeash != null) {
animT.hide(mBackgroundLeash);
@@ -301,11 +378,15 @@ public class SplitDecorManager extends WindowlessWindowManager {
animT.hide(mIconLeash);
}
}
- if (finishedConsumer != null) {
- finishedConsumer.accept(animT);
+ if (releaseSurface) {
+ releaseDecor(animT);
}
animT.apply();
animT.close();
+
+ if (mRunningAnimationCount == 0 && finishedCallback != null) {
+ finishedCallback.run();
+ }
}
});
mFadeAnimator.start();
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 419e62daf586..45b234a6398a 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
@@ -69,6 +69,7 @@ import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* Records and handles layout of splits. Helps to calculate proper bounds when configuration or
@@ -80,14 +81,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_RESIZE_DURATION = 250;
+ public static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 450;
+ private static final int FLING_EXIT_DURATION = 450;
- private final int mDividerWindowWidth;
- private final int mDividerInsets;
- private final int mDividerSize;
+ private int mDividerWindowWidth;
+ private int mDividerInsets;
+ private int mDividerSize;
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
@@ -118,8 +119,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private boolean mFreezeDividerWindow = false;
private int mOrientation;
private int mRotation;
+ private int mDensity;
private final boolean mDimNonImeSide;
+ private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
@@ -129,6 +132,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
+ mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -137,24 +141,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
- final Resources resources = context.getResources();
- mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
- mDividerInsets = getDividerInsets(resources, context.getDisplay());
- mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+ updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
resetDividerPosition();
- mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
updateInvisibleRect();
}
- private int getDividerInsets(Resources resources, Display display) {
+ private void updateDividerConfig(Context context) {
+ final Resources resources = context.getResources();
+ final Display display = context.getDisplay();
final int dividerInset = resources.getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
-
int radius = 0;
RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -165,7 +167,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
- return Math.max(dividerInset, radius);
+ mDividerInsets = Math.max(dividerInset, radius);
+ mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
}
/** Gets bounds of the primary split with screen based coordinate. */
@@ -290,9 +294,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
final int orientation = configuration.orientation;
+ final int density = configuration.densityDpi;
if (mOrientation == orientation
&& mRotation == rotation
+ && mDensity == density
&& mRootBounds.equals(rootBounds)) {
return false;
}
@@ -303,7 +309,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mTempRect.set(mRootBounds);
mRootBounds.set(rootBounds);
mRotation = rotation;
+ mDensity = density;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
@@ -388,6 +396,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mSplitWindowManager.release(t);
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+ resetDividerPosition();
}
public void release() {
@@ -441,7 +453,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
*/
void updateDivideBounds(int position) {
updateBounds(position);
- mSplitLayoutHandler.onLayoutSizeChanging(this);
+ mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
+ mSurfaceEffectPolicy.mParallaxOffset.y);
}
void setDividePosition(int position, boolean applyLayoutChange) {
@@ -569,13 +582,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
- ValueAnimator animator = ValueAnimator
+
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+
+ mDividerFlingAnimator = ValueAnimator
.ofInt(from, to)
.setDuration(duration);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addUpdateListener(
+ mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mDividerFlingAnimator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
- animator.addListener(new AnimatorListenerAdapter() {
+ mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (flingFinishedCallback != null) {
@@ -583,19 +601,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
+ mDividerFlingAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
- setDividePosition(to, true /* applyLayoutChange */);
+ mDividerFlingAnimator = null;
}
});
- animator.start();
+ mDividerFlingAnimator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
- SurfaceControl leash2, Runnable finishCallback) {
+ SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
final Rect insets = getDisplayInsets(mContext);
insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
@@ -613,18 +632,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
- // DO NOT move to insets area for smooth animation.
- distBounds1.set(distBounds1.left, distBounds1.top,
- distBounds1.right - insets.right, distBounds1.bottom - insets.bottom);
- distBounds2.set(distBounds2.left + insets.left, distBounds2.top + insets.top,
- distBounds2.right, distBounds2.bottom);
ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
- false /* alignStart */);
+ -insets.left, -insets.top);
ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
- true /* alignStart */);
+ insets.left, insets.top);
ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
- distDividerBounds, true /* alignStart */);
+ distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2, animator3);
@@ -634,14 +648,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void onAnimationEnd(Animator animation) {
mDividePosition = dividerPos;
updateBounds(mDividePosition);
- finishCallback.run();
+ finishCallback.accept(insets);
}
});
set.start();
}
private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
- Rect start, Rect end, boolean alignStart) {
+ Rect start, Rect end, float offsetX, float offsetY) {
Rect tempStart = new Rect(start);
Rect tempEnd = new Rect(end);
final float diffX = tempEnd.left - tempStart.left;
@@ -657,15 +671,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
final float distY = tempStart.top + scale * diffY;
final int width = (int) (tempStart.width() + scale * diffWidth);
final int height = (int) (tempStart.height() + scale * diffHeight);
- if (alignStart) {
+ if (offsetX == 0 && offsetY == 0) {
t.setPosition(leash, distX, distY);
t.setWindowCrop(leash, width, height);
} else {
- final int offsetX = width - tempStart.width();
- final int offsetY = height - tempStart.height();
- t.setPosition(leash, distX + offsetX, distY + offsetY);
+ final int diffOffsetX = (int) (scale * offsetX);
+ final int diffOffsetY = (int) (scale * offsetY);
+ t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
mTempRect.set(0, 0, width, height);
- mTempRect.offsetTo(-offsetX, -offsetY);
+ mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
t.setCrop(leash, mTempRect);
}
t.apply();
@@ -809,7 +823,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
* SurfaceControl, SurfaceControl, boolean)
*/
- void onLayoutSizeChanging(SplitLayout layout);
+ void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
/**
* Calls when finish resizing the split bounds.
@@ -1090,7 +1104,8 @@ 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);
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+ "onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@@ -1116,7 +1131,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);
+ mSplitWindowManager.setInteractive(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 864b9a7528b0..060ac56cae96 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
@@ -166,9 +166,9 @@ public final class SplitWindowManager extends WindowlessWindowManager {
}
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (mDividerView == null) return;
- mDividerView.setInteractive(interactive);
+ mDividerView.setInteractive(interactive, from);
}
View getDividerView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
new file mode 100644
index 000000000000..4f33a71b80d5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Configuration flags for the CompatUX implementation
+ */
+@WMSingleton
+public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
+
+ static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+
+ static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+ "enable_letterbox_reachability_education";
+
+ // Whether the extended restart dialog is enabled
+ private boolean mIsRestartDialogEnabled;
+
+ // Whether the additional education about reachability is enabled
+ private boolean mIsReachabilityEducationEnabled;
+
+ // Whether the extended restart dialog is enabled
+ private boolean mIsRestartDialogOverrideEnabled;
+
+ // Whether the additional education about reachability is enabled
+ private boolean mIsReachabilityEducationOverrideEnabled;
+
+ // Whether the extended restart dialog is allowed from backend
+ private boolean mIsLetterboxRestartDialogAllowed;
+
+ // Whether the additional education about reachability is allowed from backend
+ private boolean mIsLetterboxReachabilityEducationAllowed;
+
+ @Inject
+ public CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor) {
+ mIsRestartDialogEnabled = context.getResources().getBoolean(
+ R.bool.config_letterboxIsRestartDialogEnabled);
+ mIsReachabilityEducationEnabled = context.getResources().getBoolean(
+ R.bool.config_letterboxIsReachabilityEducationEnabled);
+ mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false);
+ mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
+ false);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
+ this);
+ }
+
+ /**
+ * @return {@value true} if the restart dialog is enabled.
+ */
+ boolean isRestartDialogEnabled() {
+ return mIsRestartDialogOverrideEnabled || (mIsRestartDialogEnabled
+ && mIsLetterboxRestartDialogAllowed);
+ }
+
+ /**
+ * Enables/Disables the restart education dialog
+ */
+ void setIsRestartDialogOverrideEnabled(boolean enabled) {
+ mIsRestartDialogOverrideEnabled = enabled;
+ }
+
+ /**
+ * @return {@value true} if the reachability education is enabled.
+ */
+ boolean isReachabilityEducationEnabled() {
+ return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+ && mIsLetterboxReachabilityEducationAllowed);
+ }
+
+ /**
+ * Enables/Disables the reachability education
+ */
+ void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
+ mIsReachabilityEducationOverrideEnabled = enabled;
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ // TODO(b/263349751): Update flag and default value to true
+ if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
+ mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
+ false);
+ }
+ if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
+ mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 235fd9c469ea..6627de58cce3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -37,6 +37,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
@@ -109,6 +110,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
+ private final DockStateReader mDockStateReader;
private CompatUICallback mCallback;
@@ -127,7 +129,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
DisplayImeController imeController,
SyncTransactionQueue syncQueue,
ShellExecutor mainExecutor,
- Lazy<Transitions> transitionsLazy) {
+ Lazy<Transitions> transitionsLazy,
+ DockStateReader dockStateReader) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -138,6 +141,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
mTransitionsLazy = transitionsLazy;
mCompatUIHintsState = new CompatUIHintsState();
shellInit.addInitCallback(this::onInit, this);
+ mDockStateReader = dockStateReader;
}
private void onInit() {
@@ -315,7 +319,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
mTransitionsLazy.get(),
- this::onLetterboxEduDismissed);
+ this::onLetterboxEduDismissed,
+ mDockStateReader);
}
private void onLetterboxEduDismissed() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
new file mode 100644
index 000000000000..4fb18e27b145
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles the shell commands for the CompatUX.
+ *
+ * <p> Use with {@code adb shell dumpsys activity service SystemUIService WMShell compatui
+ * &lt;command&gt;}.
+ */
+@WMSingleton
+public final class CompatUIShellCommandHandler implements
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+ private final ShellCommandHandler mShellCommandHandler;
+
+ @Inject
+ public CompatUIShellCommandHandler(ShellCommandHandler shellCommandHandler,
+ CompatUIConfiguration compatUIConfiguration) {
+ mShellCommandHandler = shellCommandHandler;
+ mCompatUIConfiguration = compatUIConfiguration;
+ }
+
+ void onInit() {
+ mShellCommandHandler.addCommandCallback("compatui", this, this);
+ }
+
+ @Override
+ public boolean onShellCommand(String[] args, PrintWriter pw) {
+ if (args.length != 2) {
+ pw.println("Invalid command: " + args[0]);
+ return false;
+ }
+ switch (args[0]) {
+ case "restartDialogEnabled":
+ return invokeOrError(args[1], pw,
+ mCompatUIConfiguration::setIsRestartDialogOverrideEnabled);
+ case "reachabilityEducationEnabled":
+ return invokeOrError(args[1], pw,
+ mCompatUIConfiguration::setIsReachabilityEducationOverrideEnabled);
+ default:
+ pw.println("Invalid command: " + args[0]);
+ return false;
+ }
+ }
+
+ @Override
+ public void printShellCommandHelp(PrintWriter pw, String prefix) {
+ pw.println(prefix + "restartDialogEnabled [0|false|1|true]");
+ pw.println(prefix + " Enable/Disable the restart education dialog for Size Compat Mode");
+ pw.println(prefix + "reachabilityEducationEnabled [0|false|1|true]");
+ pw.println(prefix
+ + " Enable/Disable the restart education dialog for letterbox reachability");
+ pw.println(prefix + " Disable the restart education dialog for letterbox reachability");
+ }
+
+ private static boolean invokeOrError(String input, PrintWriter pw,
+ Consumer<Boolean> setter) {
+ Boolean asBoolean = strToBoolean(input);
+ if (asBoolean == null) {
+ pw.println("Error: expected true, 1, false, 0.");
+ return false;
+ }
+ setter.accept(asBoolean);
+ return true;
+ }
+
+ // Converts a String to boolean if possible or it returns null otherwise
+ private static Boolean strToBoolean(String str) {
+ switch(str) {
+ case "1":
+ case "true":
+ return true;
+ case "0":
+ case "false":
+ return false;
+ }
+ return null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
index 3061eab17d24..7475feac5b12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
@@ -38,10 +38,15 @@ import android.view.animation.Animation;
import com.android.internal.policy.TransitionAnimation;
/**
- * Controls the enter/exit animations of the letterbox education.
+ * Controls the enter/exit a dialog.
+ *
+ * @param <T> The {@link DialogContainerSupplier} to use
*/
-class LetterboxEduAnimationController {
- private static final String TAG = "LetterboxEduAnimation";
+public class DialogAnimationController<T extends DialogContainerSupplier> {
+
+ // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
+ // 204 is simply 255 * 0.8.
+ static final int BACKGROUND_DIM_ALPHA = 204;
// If shell transitions are enabled, startEnterAnimation will be called after all transitions
// have finished, and therefore the start delay should be shorter.
@@ -49,6 +54,7 @@ class LetterboxEduAnimationController {
private final TransitionAnimation mTransitionAnimation;
private final String mPackageName;
+ private final String mTag;
@AnyRes
private final int mAnimStyleResId;
@@ -57,23 +63,24 @@ class LetterboxEduAnimationController {
@Nullable
private Animator mBackgroundDimAnimator;
- LetterboxEduAnimationController(Context context) {
- mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG);
+ public DialogAnimationController(Context context, String tag) {
+ mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, tag);
mAnimStyleResId = (new ContextThemeWrapper(context,
android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
com.android.internal.R.styleable.Window).getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
mPackageName = context.getPackageName();
+ mTag = tag;
}
/**
* Starts both background dim fade-in animation and the dialog enter animation.
*/
- void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+ public void startEnterAnimation(@NonNull T layout, Runnable endCallback) {
// Cancel any previous animation if it's still running.
cancelAnimation();
- final View dialogContainer = layout.getDialogContainer();
+ final View dialogContainer = layout.getDialogContainerView();
mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
if (mDialogAnimation == null) {
endCallback.run();
@@ -86,8 +93,8 @@ class LetterboxEduAnimationController {
endCallback.run();
}));
- mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(),
- /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
+ mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+ /* endAlpha= */ BACKGROUND_DIM_ALPHA,
mDialogAnimation.getDuration());
mBackgroundDimAnimator.addListener(getDimAnimatorListener());
@@ -101,11 +108,11 @@ class LetterboxEduAnimationController {
/**
* Starts both the background dim fade-out animation and the dialog exit animation.
*/
- void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+ public void startExitAnimation(@NonNull T layout, Runnable endCallback) {
// Cancel any previous animation if it's still running.
cancelAnimation();
- final View dialogContainer = layout.getDialogContainer();
+ final View dialogContainer = layout.getDialogContainerView();
mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
if (mDialogAnimation == null) {
endCallback.run();
@@ -119,8 +126,8 @@ class LetterboxEduAnimationController {
endCallback.run();
}));
- mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0,
- mDialogAnimation.getDuration());
+ mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+ /* endAlpha= */ 0, mDialogAnimation.getDuration());
mBackgroundDimAnimator.addListener(getDimAnimatorListener());
dialogContainer.startAnimation(mDialogAnimation);
@@ -130,7 +137,7 @@ class LetterboxEduAnimationController {
/**
* Cancels all animations and resets the state of the controller.
*/
- void cancelAnimation() {
+ public void cancelAnimation() {
if (mDialogAnimation != null) {
mDialogAnimation.cancel();
mDialogAnimation = null;
@@ -145,7 +152,7 @@ class LetterboxEduAnimationController {
Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
animAttr, /* translucent= */ false);
if (animation == null) {
- Log.e(TAG, "Failed to load animation " + animAttr);
+ Log.e(mTag, "Failed to load animation " + animAttr);
}
return animation;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
new file mode 100644
index 000000000000..7eea446fce26
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * A component which can provide a {@link View} to use as a container for a Dialog
+ */
+public interface DialogContainerSupplier {
+
+ /**
+ * @return The {@link View} to use as a container for a Dialog
+ */
+ View getDialogContainerView();
+
+ /**
+ * @return The {@link Drawable} to use as background of the dialog.
+ */
+ Drawable getBackgroundDimDrawable();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index 2e0b09e9d230..9232f36cf939 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -26,6 +26,7 @@ import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.compatui.DialogContainerSupplier;
/**
* Container for Letterbox Education Dialog and background dim.
@@ -33,11 +34,7 @@ import com.android.wm.shell.R;
* <p>This layout should fill the entire task and the background around the dialog acts as the
* background dim which dismisses the dialog when clicked.
*/
-class LetterboxEduDialogLayout extends ConstraintLayout {
-
- // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
- // 204 is simply 255 * 0.8.
- static final int BACKGROUND_DIM_ALPHA = 204;
+class LetterboxEduDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
private View mDialogContainer;
private TextView mDialogTitle;
@@ -60,16 +57,18 @@ class LetterboxEduDialogLayout extends ConstraintLayout {
super(context, attrs, defStyleAttr, defStyleRes);
}
- View getDialogContainer() {
+ @Override
+ public View getDialogContainerView() {
return mDialogContainer;
}
- TextView getDialogTitle() {
- return mDialogTitle;
+ @Override
+ public Drawable getBackgroundDimDrawable() {
+ return mBackgroundDim;
}
- Drawable getBackgroundDim() {
- return mBackgroundDim;
+ TextView getDialogTitle() {
+ return mDialogTitle;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 35f1038a6853..c14c009721a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -34,8 +34,10 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
+import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
/**
@@ -62,7 +64,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
private final SharedPreferences mSharedPreferences;
- private final LetterboxEduAnimationController mAnimationController;
+ private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
private final Transitions mTransitions;
@@ -88,19 +90,24 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
*/
private final int mDialogVerticalMargin;
+ private final DockStateReader mDockStateReader;
+
public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions,
- Runnable onDismissCallback) {
+ Runnable onDismissCallback, DockStateReader dockStateReader) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
- onDismissCallback, new LetterboxEduAnimationController(context));
+ onDismissCallback,
+ new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
+ dockStateReader);
}
@VisibleForTesting
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
- LetterboxEduAnimationController animationController) {
+ DialogAnimationController<LetterboxEduDialogLayout> animationController,
+ DockStateReader dockStateReader) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
@@ -111,6 +118,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
Context.MODE_PRIVATE);
mDialogVerticalMargin = (int) mContext.getResources().getDimension(
R.dimen.letterbox_education_dialog_margin);
+ mDockStateReader = dockStateReader;
}
@Override
@@ -130,13 +138,15 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected boolean eligibleToShowLayout() {
+ // - The letterbox education should not be visible if the device is docked.
// - If taskbar education is showing, the letterbox education shouldn't be shown for the
// given task until the taskbar education is dismissed and the compat info changes (then
// the controller will create a new instance of this class since this one isn't eligible).
// - If the layout isn't null then it was previously showing, and we shouldn't check if the
// user has seen the letterbox education before.
- return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
- || !getHasSeenLetterboxEducation());
+ return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
+ && (mLayout != null || !getHasSeenLetterboxEducation())
+ && !mDockStateReader.isDocked();
}
@Override
@@ -154,7 +164,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
if (mLayout == null) {
return;
}
- final View dialogContainer = mLayout.getDialogContainer();
+ final View dialogContainer = mLayout.getDialogContainerView();
MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams();
final Rect taskBounds = getTaskBounds();
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 c25bbbf06dda..09f5cf1d31e4 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
@@ -24,7 +24,6 @@ import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
-import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -46,6 +45,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -61,15 +61,13 @@ import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.floating.FloatingTasksController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -192,33 +190,16 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
- Context context,
- ShellInit shellInit,
- ShellCommandHandler shellCommandHandler,
- SyncTransactionQueue syncTransactionQueue,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler
- ) {
- return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
- syncTransactionQueue, displayController, displayInsetsController,
- unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
- }
-
- @WMSingleton
- @Provides
static CompatUIController provideCompatUIController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
- @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
+ @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
+ DockStateReader dockStateReader) {
return new CompatUIController(context, shellInit, shellController, displayController,
- displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
+ displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
+ dockStateReader);
}
@WMSingleton
@@ -278,13 +259,14 @@ public abstract class WMShellBaseModule {
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
- context));
+ new BackAnimationController(shellInit, shellController, shellExecutor,
+ backgroundHandler, context));
}
return Optional.empty();
}
@@ -309,17 +291,17 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
+ abstract FullscreenTaskListener optionalFullscreenTaskListener();
@WMSingleton
@Provides
- static FullscreenTaskListener<?> provideFullscreenTaskListener(
- @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
+ static FullscreenTaskListener provideFullscreenTaskListener(
+ @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
@@ -333,7 +315,7 @@ public abstract class WMShellBaseModule {
//
@BindsOptionalOf
- abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+ abstract WindowDecorViewModel optionalWindowDecorViewModel();
//
// Unfold transition
@@ -488,6 +470,7 @@ public abstract class WMShellBaseModule {
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -495,9 +478,9 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellController,
+ shellCommandHandler, taskStackListener, activityTaskManager,
+ desktopModeTaskRepository, mainExecutor));
}
//
@@ -514,14 +497,19 @@ public abstract class WMShellBaseModule {
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor);
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
+ return new Transitions(context, shellInit, shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor);
}
@WMSingleton
@@ -585,47 +573,6 @@ public abstract class WMShellBaseModule {
}
//
- // Floating tasks
- //
-
- @WMSingleton
- @Provides
- static Optional<FloatingTasks> provideFloatingTasks(
- Optional<FloatingTasksController> floatingTaskController) {
- return floatingTaskController.map((controller) -> controller.asFloatingTasks());
- }
-
- @WMSingleton
- @Provides
- static Optional<FloatingTasksController> provideFloatingTasksController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- Optional<BubbleController> bubbleController,
- WindowManager windowManager,
- ShellTaskOrganizer organizer,
- TaskViewTransitions taskViewTransitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellBackgroundThread ShellExecutor bgExecutor,
- SyncTransactionQueue syncQueue) {
- if (FloatingTasksController.FLOATING_TASKS_ENABLED) {
- return Optional.of(new FloatingTasksController(context,
- shellInit,
- shellController,
- shellCommandHandler,
- bubbleController,
- windowManager,
- organizer,
- taskViewTransitions,
- mainExecutor,
- bgExecutor,
- syncQueue));
- } else {
- return Optional.empty();
- }
- }
-
- //
// Starting window
//
@@ -638,13 +585,15 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static StartingWindowController provideStartingWindowController(Context context,
+ static StartingWindowController provideStartingWindowController(
+ Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
@@ -729,7 +678,11 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
- Optional<DesktopModeController> desktopModeController) {
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ return desktopTasksController.map(DesktopTasksController::asDesktopMode);
+ }
return desktopModeController.map(DesktopModeController::asDesktopMode);
}
@@ -739,10 +692,30 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DesktopModeController> providesDesktopModeController(
- @DynamicOverride Optional<DesktopModeController> desktopModeController) {
- if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeController;
+ static Optional<DesktopModeController> provideDesktopModeController(
+ @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isProto1Enabled()) {
+ return desktopModeController.map(Lazy::get);
+ }
+ return Optional.empty();
+ }
+
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract DesktopTasksController optionalDesktopTasksController();
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopTasksController> providesDesktopTasksController(
+ @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isProto2Enabled()) {
+ return desktopTasksController.map(Lazy::get);
}
return Optional.empty();
}
@@ -753,10 +726,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
- @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
- if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeTaskRepository;
+ static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+ @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isAnyEnabled()) {
+ return desktopModeTaskRepository.map(Lazy::get);
}
return Optional.empty();
}
@@ -781,12 +757,11 @@ public abstract class WMShellBaseModule {
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener<?> fullscreenTaskListener,
+ FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
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 37a50b611039..d3b9fa5e628d 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
@@ -50,12 +50,13 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -92,7 +93,7 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
-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;
@@ -182,22 +183,24 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static WindowDecorViewModel<?> provideWindowDecorViewModel(
+ static WindowDecorViewModel provideWindowDecorViewModel(
Context context,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- @DynamicOverride DesktopModeController desktopModeController) {
- return new CaptionWindowDecorViewModel(
- context,
- mainHandler,
- mainChoreographer,
- taskOrganizer,
- displayController,
- syncQueue,
- desktopModeController);
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController) {
+ return new DesktopModeWindowDecorViewModel(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController,
+ desktopTasksController);
}
//
@@ -208,7 +211,7 @@ public abstract class WMShellModule {
@Provides
@DynamicOverride
static FreeformComponents provideFreeformComponents(
- FreeformTaskListener<?> taskListener,
+ FreeformTaskListener taskListener,
FreeformTaskTransitionHandler transitionHandler,
FreeformTaskTransitionObserver transitionObserver) {
return new FreeformComponents(
@@ -217,18 +220,18 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static FreeformTaskListener<?> provideFreeformTaskListener(
+ static FreeformTaskListener provideFreeformTaskListener(
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
ShellInit init = FreeformComponents.isFreeformEnabled(context)
? shellInit
: null;
- return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
+ return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
windowDecorViewModel);
}
@@ -237,7 +240,7 @@ public abstract class WMShellModule {
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
}
@@ -247,10 +250,9 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionObserver(
- context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
+ context, shellInit, transitions, windowDecorViewModel);
}
//
@@ -599,7 +601,9 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+ static DesktopModeController provideDesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -607,7 +611,7 @@ public abstract class WMShellModule {
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
@@ -615,11 +619,49 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
+ static DesktopTasksController provideDesktopTasksController(
+ Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Transitions transitions,
+ @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
+ transitions, desktopModeTaskRepository, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
return new DesktopModeTaskRepository();
}
//
+ // Kids mode
+ //
+ @WMSingleton
+ @Provides
+ static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+ Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler
+ ) {
+ return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+ syncTransactionQueue, displayController, displayInsetsController,
+ unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
+ }
+
+ //
// Misc
//
@@ -630,6 +672,7 @@ public abstract class WMShellModule {
@Provides
static Object provideIndependentShellComponentsToCreate(
DefaultMixedHandler defaultMixedHandler,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<DesktopModeController> desktopModeController) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38d09e1..cbd544cc4b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,14 +18,21 @@ package com.android.wm.shell.desktopmode;
import com.android.wm.shell.common.annotations.ExternalThread;
+import java.util.concurrent.Executor;
+
/**
* Interface to interact with desktop mode feature in shell.
*/
@ExternalThread
public interface DesktopMode {
- /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
- default IDesktopMode createExternalInterface() {
- return null;
- }
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor);
+
}
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 99739c457aa6..f5f3573252ec 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
@@ -16,12 +16,18 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -29,44 +35,58 @@ import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+ Transitions.TransitionHandler {
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
- private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
- public DesktopModeController(Context context, ShellInit shellInit,
+ public DesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -74,21 +94,27 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTransitions = transitions;
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- shellInit.addInitCallback(this::onInit, this);
+ if (DesktopModeStatus.isProto1Enabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+ this::createExternalInterface, this);
mSettingsObserver.observe();
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
+ mTransitions.addHandler(this);
}
@Override
@@ -108,27 +134,42 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
return mDesktopModeImpl;
}
+ /**
+ * Creates a new instance of the external interface to pass to another process.
+ */
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDesktopModeImpl(this);
+ }
+
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor) {
+ mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
int displayId = mContext.getDisplayId();
+ ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level (tasks should inherit
- // container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
- true /* transfer */);
- int targetWindowingMode;
+ // Reset freeform windowing mode that is set per task level so tasks inherit it
+ clearFreeformForStandardTasks(runningTasks, wct);
if (active) {
- targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ moveHomeBehindVisibleTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
} else {
- targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
- // Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
- true /* transfer */);
+ clearBoundsForStandardTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
}
- prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
@@ -136,17 +177,69 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
}
- private void prepareWindowingModeChange(WindowContainerTransaction wct,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
- .getDisplayAreaInfo(displayId);
+ private WindowContainerTransaction clearBoundsForStandardTasks(
+ ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ }
+
+ private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+ RunningTaskInfo homeTask = null;
+ ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ homeTask = taskInfo;
+ } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.isVisible()) {
+ visibleTasks.add(taskInfo);
+ }
+ }
+ if (homeTask == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+ } else {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+ visibleTasks.size());
+ wct.reorder(homeTask.getToken(), true /* onTop */);
+ for (RunningTaskInfo task : visibleTasks) {
+ wct.reorder(task.getToken(), true /* onTop */);
+ }
+ }
+ }
+
+ private void setDisplayAreaWindowingMode(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ displayId);
if (displayAreaInfo == null) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
"unable to update windowing mode for display %d display not found", displayId);
return;
}
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
windowingMode);
@@ -157,23 +250,52 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
/**
* Show apps on desktop
*/
- public void showDesktopApps() {
- ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+ void showDesktopApps() {
+ WindowContainerTransaction wct = bringDesktopAppsToFront();
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
+ } else {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ @NonNull
+ private WindowContainerTransaction bringDesktopAppsToFront() {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
- ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+ final List<RunningTaskInfo> taskInfos = new ArrayList<>();
for (Integer taskId : activeTasks) {
RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
if (taskInfo != null) {
taskInfos.add(taskInfo);
}
}
- // Order by lastActiveTime, descending
- taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
- WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (taskInfos.isEmpty()) {
+ return wct;
+ }
+
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: reordering all active tasks to the front");
+ final List<Integer> allTasksInZOrder =
+ mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+ // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+ // in the WCT.
+ taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- mShellTaskOrganizer.applyTransaction(wct);
+ return wct;
}
/**
@@ -195,6 +317,49 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
.configuration.windowConfiguration.getWindowingMode();
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
+ // freeform
+ if (!DesktopModeStatus.isActive(mContext)) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "skip shell transition request: desktop mode not active");
+ return null;
+ }
+ if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "skip shell transition request: unsupported type %s",
+ WindowManager.transitTypeToString(request.getType()));
+ return null;
+ }
+ if (request.getTriggerTask() == null
+ || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
+ return null;
+ }
+ 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();
+ }
+ wct.merge(bringDesktopAppsToFront(), true /* transfer */);
+ wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+ return wct;
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -236,15 +401,12 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
- private IDesktopModeImpl mIDesktopMode;
-
@Override
- public IDesktopMode createExternalInterface() {
- if (mIDesktopMode != null) {
- mIDesktopMode.invalidate();
- }
- mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
- return mIDesktopMode;
+ public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor) {
+ mMainExecutor.execute(() -> {
+ DesktopModeController.this.addListener(listener, callbackExecutor);
+ });
}
}
@@ -252,7 +414,8 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub {
+ private static class IDesktopModeImpl extends IDesktopMode.Stub
+ implements ExternalInterfaceBinder {
private DesktopModeController mController;
@@ -263,7 +426,8 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 195ff502e7dc..055949fd8c89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -33,22 +33,53 @@ public class DesktopModeStatus {
/**
* Flag to indicate whether desktop mode is available on the device
*/
- public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+ private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
/**
+ * Flag to indicate whether desktop mode proto 2 is available on the device
+ */
+ private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_2", false);
+
+ /**
+ * Return {@code true} if desktop mode support is enabled
+ */
+ public static boolean isProto1Enabled() {
+ return IS_SUPPORTED;
+ }
+
+ /**
+ * Return {@code true} is desktop windowing proto 2 is enabled
+ */
+ public static boolean isProto2Enabled() {
+ return IS_PROTO2_ENABLED;
+ }
+
+ /**
+ * Return {@code true} if proto 1 or 2 is enabled.
+ * Can be used to guard logic that is common for both prototypes.
+ */
+ public static boolean isAnyEnabled() {
+ return isProto1Enabled() || isProto2Enabled();
+ }
+
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
*/
public static boolean isActive(Context context) {
- if (!IS_SUPPORTED) {
+ if (!isAnyEnabled()) {
return false;
}
+ if (isProto2Enabled()) {
+ // Desktop mode is always active in prototype 2
+ return true;
+ }
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 988601c0e8a8..600ccc17ecaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,7 +16,9 @@
package com.android.wm.shell.desktopmode
+import android.util.ArrayMap
import android.util.ArraySet
+import java.util.concurrent.Executor
/**
* Keeps track of task data related to desktop mode.
@@ -30,40 +32,67 @@ class DesktopModeTaskRepository {
* Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
*/
private val activeTasks = ArraySet<Int>()
- private val listeners = ArraySet<Listener>()
+ private val visibleTasks = ArraySet<Int>()
+ // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+ private val freeformTasksInZOrder = mutableListOf<Int>()
+ private val activeTasksListeners = ArraySet<ActiveTasksListener>()
+ // Track visible tasks separately because a task may be part of the desktop but not visible.
+ private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
/**
- * Add a [Listener] to be notified of updates to the repository.
+ * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
*/
- fun addListener(listener: Listener) {
- listeners.add(listener)
+ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
+ activeTasksListeners.add(activeTasksListener)
}
/**
- * Remove a previously registered [Listener]
+ * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
*/
- fun removeListener(listener: Listener) {
- listeners.remove(listener)
+ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
+ visibleTasksListeners.put(visibleTasksListener, executor)
+ executor.execute(
+ Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) })
+ }
+
+ /**
+ * Remove a previously registered [ActiveTasksListener]
+ */
+ fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
+ activeTasksListeners.remove(activeTasksListener)
+ }
+
+ /**
+ * Remove a previously registered [VisibleTasksListener]
+ */
+ fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
+ visibleTasksListeners.remove(visibleTasksListener)
}
/**
* Mark a task with given [taskId] as active.
+ *
+ * @return `true` if the task was not active
*/
- fun addActiveTask(taskId: Int) {
+ fun addActiveTask(taskId: Int): Boolean {
val added = activeTasks.add(taskId)
if (added) {
- listeners.onEach { it.onActiveTasksChanged() }
+ activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return added
}
/**
* Remove task with given [taskId] from active tasks.
+ *
+ * @return `true` if the task was active
*/
- fun removeActiveTask(taskId: Int) {
+ fun removeActiveTask(taskId: Int): Boolean {
val removed = activeTasks.remove(taskId)
if (removed) {
- listeners.onEach { it.onActiveTasksChanged() }
+ activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return removed
}
/**
@@ -74,6 +103,13 @@ class DesktopModeTaskRepository {
}
/**
+ * Whether a task is visible.
+ */
+ fun isVisibleTask(taskId: Int): Boolean {
+ return visibleTasks.contains(taskId)
+ }
+
+ /**
* Get a set of the active tasks
*/
fun getActiveTasks(): ArraySet<Int> {
@@ -81,9 +117,67 @@ class DesktopModeTaskRepository {
}
/**
- * Defines interface for classes that can listen to changes in repository state.
+ * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+ */
+ fun getFreeformTasksInZOrder(): List<Int> {
+ return freeformTasksInZOrder
+ }
+
+ /**
+ * Updates whether a freeform task with this id is visible or not and notifies listeners.
+ */
+ fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
+ val prevCount: Int = visibleTasks.size
+ if (visible) {
+ visibleTasks.add(taskId)
+ } else {
+ visibleTasks.remove(taskId)
+ }
+ if (prevCount == 0 && visibleTasks.size == 1 ||
+ prevCount > 0 && visibleTasks.size == 0) {
+ for ((listener, executor) in visibleTasksListeners) {
+ executor.execute(
+ Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) })
+ }
+ }
+ }
+
+ /**
+ * Add (or move if it already exists) the task to the top of the ordered list.
+ */
+ fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ if (freeformTasksInZOrder.contains(taskId)) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+ freeformTasksInZOrder.add(0, taskId)
+ }
+
+ /**
+ * Remove the task from the ordered list.
+ */
+ fun removeFreeformTask(taskId: Int) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+
+ /**
+ * Defines interface for classes that can listen to changes for active tasks in desktop mode.
+ */
+ interface ActiveTasksListener {
+ /**
+ * Called when the active tasks change in desktop mode.
+ */
+ @JvmDefault
+ fun onActiveTasksChanged() {}
+ }
+
+ /**
+ * Defines interface for classes that can listen to changes for visible tasks in desktop mode.
*/
- interface Listener {
- fun onActiveTasksChanged()
+ interface VisibleTasksListener {
+ /**
+ * Called when the desktop starts or stops showing freeform tasks.
+ */
+ @JvmDefault
+ fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
new file mode 100644
index 000000000000..3341470efe4d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.WindowingMode
+import android.content.Context
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.annotation.BinderThread
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.ExecutorUtils
+import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.RemoteCallable
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.annotations.ExternalThread
+import com.android.wm.shell.common.annotations.ShellMainThread
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.ShellSharedConstants
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+/** Handles moving tasks in and out of desktop */
+class DesktopTasksController(
+ private val context: Context,
+ shellInit: ShellInit,
+ private val shellController: ShellController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val transitions: Transitions,
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ @ShellMainThread private val mainExecutor: ShellExecutor
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
+
+ private val desktopMode: DesktopModeImpl
+
+ init {
+ desktopMode = DesktopModeImpl()
+ if (DesktopModeStatus.isProto2Enabled()) {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+ }
+
+ private fun onInit() {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ shellController.addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
+ { createExternalInterface() },
+ this
+ )
+ transitions.addHandler(this)
+ }
+
+ /** Show all tasks, that are part of the desktop, on top of launcher */
+ fun showDesktopApps() {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
+ val wct = WindowContainerTransaction()
+
+ bringDesktopAppsToFront(wct)
+
+ // Execute transaction if there are pending operations
+ if (!wct.isEmpty) {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+ }
+
+ /** Move a task with given `taskId` to desktop */
+ fun moveToDesktop(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
+ }
+
+ /** Move a task to desktop */
+ fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
+
+ val wct = WindowContainerTransaction()
+ // Bring other apps to front first
+ bringDesktopAppsToFront(wct)
+
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
+ wct.reorder(task.getToken(), true /* onTop */)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /** Move a task with given `taskId` to fullscreen */
+ fun moveToFullscreen(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
+ }
+
+ /** Move a task to fullscreen */
+ fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(task.getToken(), null)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /**
+ * Get windowing move for a given `taskId`
+ *
+ * @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
+ */
+ @WindowingMode
+ fun getTaskWindowingMode(taskId: Int): Int {
+ return shellTaskOrganizer.getRunningTaskInfo(taskId)?.windowingMode
+ ?: WINDOWING_MODE_UNDEFINED
+ }
+
+ private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
+
+ // Skip if all tasks are already visible
+ if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping."
+ )
+ return
+ }
+
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+
+ // First move home to front and then other tasks on top of it
+ moveHomeTaskToFront(wct)
+
+ val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder()
+ activeTasks
+ // Sort descending as the top task is at index 0. It should be ordered to top last
+ .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) }
+ .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
+ .forEach { task -> wct.reorder(task.token, true /* onTop */) }
+ }
+
+ private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+ shellTaskOrganizer
+ .getRunningTasks(context.displayId)
+ .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+ }
+
+ override fun getContext(): Context {
+ return context
+ }
+
+ override fun getRemoteCallExecutor(): ShellExecutor {
+ return mainExecutor
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback
+ ): Boolean {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ // Check if we should skip handling this transition
+ val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+ val shouldHandleRequest =
+ when {
+ // Only handle open or to front transitions
+ request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+ // Only handle when it is a task transition
+ task == null -> false
+ // Only handle standard type tasks
+ task.activityType != ACTIVITY_TYPE_STANDARD -> false
+ // Only handle fullscreen or freeform tasks
+ task.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+ task.windowingMode != WINDOWING_MODE_FREEFORM -> false
+ // Otherwise process it
+ else -> true
+ }
+
+ if (!shouldHandleRequest) {
+ return null
+ }
+
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
+
+ // Check if we should switch a fullscreen task to freeform
+ if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // If there are any visible desktop tasks, switch the task to freeform
+ if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController#handleRequest: switch fullscreen task to freeform," +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().apply {
+ setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ }
+ }
+ }
+
+ // CHeck if we should switch a freeform task to fullscreen
+ if (task?.windowingMode == WINDOWING_MODE_FREEFORM) {
+ // If no visible desktop tasks, switch this task to freeform as the transition came
+ // outside of this controller
+ if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController#handleRequest: switch freeform task to fullscreen," +
+ " taskId=%d",
+ task.taskId
+ )
+ return WindowContainerTransaction().apply {
+ setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
+ setBounds(task.token, null)
+ }
+ }
+ }
+ return null
+ }
+
+ /** Creates a new instance of the external interface to pass to another process. */
+ private fun createExternalInterface(): ExternalInterfaceBinder {
+ return IDesktopModeImpl(this)
+ }
+
+ /** Get connection interface between sysui and shell */
+ fun asDesktopMode(): DesktopMode {
+ return desktopMode
+ }
+
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+ desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor)
+ }
+
+ /** The interface for calls from outside the shell, within the host process. */
+ @ExternalThread
+ private inner class DesktopModeImpl : DesktopMode {
+ override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+ mainExecutor.execute {
+ this@DesktopTasksController.addListener(listener, callbackExecutor)
+ }
+ }
+ }
+
+ /** The interface for calls from outside the host process. */
+ @BinderThread
+ private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
+ IDesktopMode.Stub(), ExternalInterfaceBinder {
+ /** Invalidates this instance, preventing future calls from updating the controller. */
+ override fun invalidate() {
+ controller = null
+ }
+
+ override fun showDesktopApps() {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "showDesktopApps",
+ Consumer(DesktopTasksController::showDesktopApps)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 000000000000..926cfb3b12ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d641fa..fbf326eadcd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@ As mentioned in the [Dagger usage](dagger.md) docs, you need to determine whethe
### SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+ controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
### Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+ extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+ references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+ the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+ and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+ call the SystemUIProxy method as needed in that code
+
### Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@ adb logcat *:S WindowManagerShell
### General Do's & Dont's
Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 62bf5172e106..d93a9012c8f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -240,7 +240,7 @@ public class DragAndDropPolicy {
// Update launch options for the split side we are targeting.
position = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
// Add some data for logging splitscreen once it is invoked
- mSplitScreen.logOnDroppedToSplit(position, mLoggerSessionId);
+ mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
final ClipDescription description = data.getDescription();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 497a6f696df8..55378a826385 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -311,7 +311,7 @@ public class DragLayout extends LinearLayout {
animateSplitContainers(true, null /* animCompleteCallback */);
animateHighlight(target);
}
- } else {
+ } else if (mCurrentTarget.type != target.type) {
// Switching between targets
mDropZoneView1.animateSwitch();
mDropZoneView2.animateSwitch();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
deleted file mode 100644
index 83a1734dc71a..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
+++ /dev/null
@@ -1,259 +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.floating;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-
-import java.util.Objects;
-
-/**
- * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved
- * close to the target to be stuck to it unless moved out again.
- */
-public class FloatingDismissController {
-
- /** Velocity required to dismiss the view without dragging it into the dismiss target. */
- private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
- /**
- * Max velocity that the view can be moving through the target with to stick (i.e. if it's
- * more than this velocity, it will pass through the target.
- */
- private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f;
- /**
- * Percentage of the target width to use to determine if an object flung towards the target
- * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a
- * 200px-wide area around the target will be considered 'near' enough get dismissed).
- */
- private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f;
- /** Minimum alpha to apply to the view being dismissed when it is in the target. */
- private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f;
- /** Amount to scale down the view being dismissed when it is in the target. */
- private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f;
-
- private Context mContext;
- private FloatingTasksController mController;
- private FloatingTaskLayer mParent;
-
- private DismissView mDismissView;
- private ValueAnimator mDismissAnimator;
- private View mViewBeingDismissed;
- private float mDismissSizePercent;
- private float mDismissSize;
-
- /**
- * The currently magnetized object, which is being dragged and will be attracted to the magnetic
- * dismiss target.
- */
- private MagnetizedObject<View> mMagnetizedObject;
- /**
- * The MagneticTarget instance for our circular dismiss view. This is added to the
- * MagnetizedObject instances for the view being dragged.
- */
- private MagnetizedObject.MagneticTarget mMagneticTarget;
- /** Magnet listener that handles animating and dismissing the view. */
- private MagnetizedObject.MagnetListener mFloatingViewMagnetListener;
-
- public FloatingDismissController(Context context, FloatingTasksController controller,
- FloatingTaskLayer parent) {
- mContext = context;
- mController = controller;
- mParent = parent;
- updateSizes();
- createAndAddDismissView();
-
- mDismissAnimator = ValueAnimator.ofFloat(1f, 0f);
- mDismissAnimator.addUpdateListener(animation -> {
- final float value = (float) animation.getAnimatedValue();
- if (mDismissView != null) {
- mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
- mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
- final float scaleValue = Math.max(value, mDismissSizePercent);
- mDismissView.getCircle().setScaleX(scaleValue);
- mDismissView.getCircle().setScaleY(scaleValue);
- }
- if (mViewBeingDismissed != null) {
- // TODO: alpha doesn't actually apply to taskView currently.
- mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA));
- mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
- mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
- }
- });
-
- mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(
- @NonNull MagnetizedObject.MagneticTarget target) {
- animateDismissing(/* dismissing= */ true);
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- animateDismissing(/* dismissing= */ false);
- mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY,
- wasFlungOut);
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- doDismiss();
- }
- };
- }
-
- /** Updates all the sizes used and applies them to the {@link DismissView}. */
- public void updateSizes() {
- Resources res = mContext.getResources();
- mDismissSize = res.getDimensionPixelSize(
- R.dimen.floating_task_dismiss_circle_size);
- final float minDismissSize = res.getDimensionPixelSize(
- R.dimen.floating_dismiss_circle_small);
- mDismissSizePercent = minDismissSize / mDismissSize;
-
- if (mDismissView != null) {
- mDismissView.updateResources();
- }
- }
-
- /** Prepares the view being dragged to be magnetic. */
- public void setUpMagneticObject(View viewBeingDragged) {
- mViewBeingDismissed = viewBeingDragged;
- mMagnetizedObject = getMagnetizedView(viewBeingDragged);
- mMagnetizedObject.clearAllTargets();
- mMagnetizedObject.addTarget(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener);
- }
-
- /** Shows or hides the dismiss target. */
- public void showDismiss(boolean show) {
- if (show) {
- mDismissView.show();
- } else {
- mDismissView.hide();
- }
- }
-
- /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
- public boolean passEventToMagnetizedObject(MotionEvent event) {
- return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
- }
-
- private void createAndAddDismissView() {
- if (mDismissView != null) {
- mParent.removeView(mDismissView);
- }
- mDismissView = new DismissView(mContext);
- mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size);
- mDismissView.updateResources();
- mParent.addView(mDismissView);
-
- final float dismissRadius = mDismissSize;
- // Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects when the dismiss view gets shown.
- mMagneticTarget = new MagnetizedObject.MagneticTarget(
- mDismissView.getCircle(), (int) dismissRadius);
- }
-
- private MagnetizedObject<View> getMagnetizedView(View v) {
- if (mMagnetizedObject != null
- && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) {
- // Same view being dragged, we can reuse the magnetic object.
- return mMagnetizedObject;
- }
- MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>(
- mContext,
- v,
- DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y
- ) {
- @Override
- public float getWidth(@NonNull View underlyingObject) {
- return underlyingObject.getWidth();
- }
-
- @Override
- public float getHeight(@NonNull View underlyingObject) {
- return underlyingObject.getHeight();
- }
-
- @Override
- public void getLocationOnScreen(@NonNull View underlyingObject,
- @NonNull int[] loc) {
- loc[0] = (int) underlyingObject.getTranslationX();
- loc[1] = (int) underlyingObject.getTranslationY();
- }
- };
- magnetizedView.setHapticsEnabled(true);
- magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY);
- magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT);
- return magnetizedView;
- }
-
- /** Animates the dismiss treatment on the view being dismissed. */
- private void animateDismissing(boolean shouldDismiss) {
- if (mViewBeingDismissed == null) {
- return;
- }
- if (shouldDismiss) {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.start();
- } else {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- resetDismissAnimator();
- }
- });
- mDismissAnimator.reverse();
- }
- }
-
- /** Actually dismisses the view. */
- private void doDismiss() {
- mDismissView.hide();
- mController.removeTask();
- resetDismissAnimator();
- mViewBeingDismissed = null;
- }
-
- private void resetDismissAnimator() {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.cancel();
- if (mDismissView != null) {
- mDismissView.cancelAnimators();
- mDismissView.getCircle().setScaleX(1f);
- mDismissView.getCircle().setScaleY(1f);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
deleted file mode 100644
index 935666026bf4..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ /dev/null
@@ -1,41 +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.floating;
-
-import android.content.Intent;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to interact with floating tasks.
- */
-@ExternalThread
-public interface FloatingTasks {
-
- /**
- * Shows, stashes, or un-stashes the floating task depending on state:
- * - If there is no floating task for this intent, it shows the task for the provided intent.
- * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
- * - If there is a floating task for this intent, and it's not stashed, this stashes it.
- */
- void showOrSetStashed(Intent intent);
-
- /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
- default IFloatingTasks createExternalInterface() {
- return null;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
deleted file mode 100644
index 67552991869b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ /dev/null
@@ -1,455 +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.floating;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Entry point for creating and managing floating tasks.
- *
- * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView}
- * within that window.
- *
- * Currently optimized for a single task. Multiple tasks are not supported.
- */
-public class FloatingTasksController implements RemoteCallable<FloatingTasksController>,
- ConfigurationChangeListener {
-
- private static final String TAG = FloatingTasksController.class.getSimpleName();
-
- public static final boolean FLOATING_TASKS_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
- public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false);
-
- @VisibleForTesting
- static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600;
-
- // Only used for testing
- private Configuration mConfig;
- private boolean mFloatingTasksEnabledForTests;
-
- private FloatingTaskImpl mImpl = new FloatingTaskImpl();
- private Context mContext;
- private ShellController mShellController;
- private ShellCommandHandler mShellCommandHandler;
- private @Nullable BubbleController mBubbleController;
- private WindowManager mWindowManager;
- private ShellTaskOrganizer mTaskOrganizer;
- private TaskViewTransitions mTaskViewTransitions;
- private @ShellMainThread ShellExecutor mMainExecutor;
- // TODO: mBackgroundThread is not used but we'll probs need it eventually?
- private @ShellBackgroundThread ShellExecutor mBackgroundThread;
- private SyncTransactionQueue mSyncQueue;
-
- private boolean mIsFloatingLayerAdded;
- private FloatingTaskLayer mFloatingTaskLayer;
- private final Point mLastPosition = new Point(-1, -1);
-
- private Task mTask;
-
- // Simple class to hold onto info for intent or shortcut based tasks.
- public static class Task {
- public int taskId = INVALID_TASK_ID;
- @Nullable
- public Intent intent;
- @Nullable
- public ShortcutInfo info;
- @Nullable
- public FloatingTaskView floatingView;
- }
-
- public FloatingTasksController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- Optional<BubbleController> bubbleController,
- WindowManager windowManager,
- ShellTaskOrganizer organizer,
- TaskViewTransitions transitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellBackgroundThread ShellExecutor bgExceutor,
- SyncTransactionQueue syncTransactionQueue) {
- mContext = context;
- mShellController = shellController;
- mShellCommandHandler = shellCommandHandler;
- mBubbleController = bubbleController.get();
- mWindowManager = windowManager;
- mTaskOrganizer = organizer;
- mTaskViewTransitions = transitions;
- mMainExecutor = mainExecutor;
- mBackgroundThread = bgExceutor;
- mSyncQueue = syncTransactionQueue;
- if (isFloatingTasksEnabled()) {
- shellInit.addInitCallback(this::onInit, this);
- }
- mShellCommandHandler.addDumpCallback(this::dump, this);
- }
-
- protected void onInit() {
- mShellController.addConfigurationChangeListener(this);
- }
-
- /** Only used for testing. */
- @VisibleForTesting
- void setConfig(Configuration config) {
- mConfig = config;
- }
-
- /** Only used for testing. */
- @VisibleForTesting
- void setFloatingTasksEnabled(boolean enabled) {
- mFloatingTasksEnabledForTests = enabled;
- }
-
- /** Whether the floating layer is available. */
- boolean isFloatingLayerAvailable() {
- Configuration config = mConfig == null
- ? mContext.getResources().getConfiguration()
- : mConfig;
- return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
- }
-
- /** Whether floating tasks are enabled. */
- boolean isFloatingTasksEnabled() {
- return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
- }
-
- @Override
- public void onThemeChanged() {
- if (mIsFloatingLayerAdded) {
- mFloatingTaskLayer.updateSizes();
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- // TODO: probably other stuff here to do (e.g. handle rotation)
- if (mIsFloatingLayerAdded) {
- mFloatingTaskLayer.updateSizes();
- }
- }
-
- /** Returns false if the task shouldn't be shown. */
- private boolean canShowTask(Intent intent) {
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask -- %s", intent);
- if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false;
- if (intent == null) {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing");
- return false;
- }
- return true;
- }
-
- /** Returns true if the task was or should be shown as a bubble. */
- private boolean maybeShowTaskAsBubble(Intent intent) {
- if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) {
- removeFloatingLayer();
- if (intent.getPackage() != null) {
- mBubbleController.addAppBubble(intent);
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent);
- } else {
- ProtoLog.d(WM_SHELL_FLOATING_APPS,
- "failed to show floating task as bubble: %s; unknown package", intent);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Shows, stashes, or un-stashes the floating task depending on state:
- * - If there is no floating task for this intent, it shows this the provided task.
- * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
- * - If there is a floating task for this intent, and it's not stashed, this stashes it.
- */
- public void showOrSetStashed(Intent intent) {
- if (!canShowTask(intent)) return;
- if (maybeShowTaskAsBubble(intent)) return;
-
- addFloatingLayer();
-
- if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
- // The task is already added, toggle the stash state.
- mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed());
- return;
- }
-
- // If we're here it's either a new or different task
- showNewTask(intent);
- }
-
- /**
- * Shows a floating task with the provided intent.
- * If the same task is present it will un-stash it or do nothing if it is already un-stashed.
- * Removes any other floating tasks that might exist.
- */
- public void showTask(Intent intent) {
- if (!canShowTask(intent)) return;
- if (maybeShowTaskAsBubble(intent)) return;
-
- addFloatingLayer();
-
- if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
- // The task is already added, show it if it's stashed.
- if (mTask.floatingView.isStashed()) {
- mFloatingTaskLayer.setStashed(mTask, false);
- }
- return;
- }
- showNewTask(intent);
- }
-
- private void showNewTask(Intent intent) {
- if (mTask != null && !intent.filterEquals(mTask.intent)) {
- mFloatingTaskLayer.removeAllTaskViews();
- mTask.floatingView.cleanUpTaskView();
- mTask = null;
- }
-
- FloatingTaskView ftv = new FloatingTaskView(mContext, this);
- ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
-
- mTask = new Task();
- mTask.floatingView = ftv;
- mTask.intent = intent;
-
- // Add & start the task.
- mFloatingTaskLayer.addTask(mTask);
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent);
- mTask.floatingView.startTask(mMainExecutor, mTask);
- }
-
- /**
- * Removes the task and cleans up the view.
- */
- public void removeTask() {
- if (mTask != null) {
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId);
-
- if (mTask.floatingView != null) {
- // TODO: animate it
- mFloatingTaskLayer.removeView(mTask.floatingView);
- mTask.floatingView.cleanUpTaskView();
- }
- removeFloatingLayer();
- }
- }
-
- /**
- * Whether there is a floating task and if it is stashed.
- */
- public boolean isStashed() {
- return isTaskAttached(mTask) && mTask.floatingView.isStashed();
- }
-
- /**
- * If a floating task exists, this sets whether it is stashed and animates if needed.
- */
- public void setStashed(boolean shouldStash) {
- if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) {
- mFloatingTaskLayer.setStashed(mTask, shouldStash);
- }
- }
-
- /**
- * Saves the last position the floating task was in so that it can be put there again.
- */
- public void setLastPosition(int x, int y) {
- mLastPosition.set(x, y);
- }
-
- /**
- * Returns the last position the floating task was in.
- */
- public Point getLastPosition() {
- return mLastPosition;
- }
-
- /**
- * Whether the provided task has a view that's attached to the floating layer.
- */
- private boolean isTaskAttached(Task t) {
- return t != null && t.floatingView != null
- && mIsFloatingLayerAdded
- && mFloatingTaskLayer.getTaskViewCount() > 0
- && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView);
- }
-
- // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this
- // type allowed? Bubbles & floating tasks should probably be in the same layer to reduce
- // # of windows.
- private void addFloatingLayer() {
- if (mIsFloatingLayerAdded) {
- return;
- }
-
- mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager);
-
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
- PixelFormat.TRANSLUCENT
- );
- params.setTrustedOverlay();
- params.setFitInsetsTypes(0);
- params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- params.setTitle("FloatingTaskLayer");
- params.packageName = mContext.getPackageName();
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- try {
- mIsFloatingLayerAdded = true;
- mWindowManager.addView(mFloatingTaskLayer, params);
- } catch (IllegalStateException e) {
- // This means the floating layer has already been added which shouldn't happen.
- e.printStackTrace();
- }
- }
-
- private void removeFloatingLayer() {
- if (!mIsFloatingLayerAdded) {
- return;
- }
- try {
- mIsFloatingLayerAdded = false;
- if (mFloatingTaskLayer != null) {
- mWindowManager.removeView(mFloatingTaskLayer);
- }
- } catch (IllegalArgumentException e) {
- // This means the floating layer has already been removed which shouldn't happen.
- e.printStackTrace();
- }
- }
-
- /**
- * Description of current floating task state.
- */
- private void dump(PrintWriter pw, String prefix) {
- pw.println("FloatingTaskController state:");
- pw.print(" isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable());
- pw.print(" isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled());
- pw.print(" mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded);
- pw.print(" mLastPosition= "); pw.println(mLastPosition);
- pw.println();
- }
-
- /** Returns the {@link FloatingTasks} implementation. */
- public FloatingTasks asFloatingTasks() {
- return mImpl;
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- /**
- * The interface for calls from outside the shell, within the host process.
- */
- @ExternalThread
- private class FloatingTaskImpl implements FloatingTasks {
- private IFloatingTasksImpl mIFloatingTasks;
-
- @Override
- public void showOrSetStashed(Intent intent) {
- mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
- }
-
- @Override
- public IFloatingTasks createExternalInterface() {
- if (mIFloatingTasks != null) {
- mIFloatingTasks.invalidate();
- }
- mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
- return mIFloatingTasks;
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
- private FloatingTasksController mController;
-
- IFloatingTasksImpl(FloatingTasksController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- void invalidate() {
- mController = null;
- }
-
- public void showTask(Intent intent) {
- executeRemoteCallWithTaskPermission(mController, "showTask",
- (controller) -> controller.showTask(intent));
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
deleted file mode 100644
index c922109751ba..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
+++ /dev/null
@@ -1,73 +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.floating.views;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Displays the menu items for a floating task view (e.g. close).
- */
-public class FloatingMenuView extends LinearLayout {
-
- private int mItemSize;
- private int mItemMargin;
-
- public FloatingMenuView(Context context) {
- super(context);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
-
- mItemSize = context.getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_item_size);
- mItemMargin = context.getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_item_padding);
- }
-
- /** Adds a clickable item to the menu bar. Items are ordered as added. */
- public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) {
- ImageView itemView = new ImageView(getContext());
- itemView.setScaleType(ImageView.ScaleType.CENTER);
- if (drawable != null) {
- itemView.setImageDrawable(drawable);
- }
- LinearLayout.LayoutParams lp = new LayoutParams(mItemSize,
- ViewGroup.LayoutParams.MATCH_PARENT);
- lp.setMarginStart(mItemMargin);
- lp.setMarginEnd(mItemMargin);
- addView(itemView, lp);
-
- itemView.setOnClickListener(listener);
- }
-
- /**
- * The menu extends past the top of the TaskView because of the rounded corners. This means
- * to center content in the menu we must subtract the radius (i.e. the amount of space covered
- * by TaskView).
- */
- public void setCornerRadius(float radius) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
deleted file mode 100644
index 16dab2415bf2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
+++ /dev/null
@@ -1,687 +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.floating.views;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FlingAnimation;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.floating.FloatingDismissController;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and
- * movement of the task views.
- */
-public class FloatingTaskLayer extends FrameLayout
- implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
- private static final String TAG = FloatingTaskLayer.class.getSimpleName();
-
- /** How big to make the task view based on screen width of the largest size. */
- private static final float START_SIZE_WIDTH_PERCENT = 0.33f;
- /** Min fling velocity required to move the view from one side of the screen to the other. */
- private static final float ESCAPE_VELOCITY = 750f;
- /** Amount of friction to apply to fling animations. */
- private static final float FLING_FRICTION = 1.9f;
-
- private final FloatingTasksController mController;
- private final FloatingDismissController mDismissController;
- private final WindowManager mWindowManager;
- private final TouchHandlerImpl mTouchHandler;
-
- private final Region mTouchableRegion = new Region();
- private final Rect mPositionRect = new Rect();
- private final Point mDefaultStartPosition = new Point();
- private final Point mTaskViewSize = new Point();
- private WindowInsets mWindowInsets;
- private int mVerticalPadding;
- private int mOverhangWhenStashed;
-
- private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
- private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener =
- this::updateSystemGestureExclusion;
-
- /** Interface allowing something to handle the touch events going to a task. */
- interface FloatingTaskTouchHandler {
- void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float viewInitialX, float viewInitialY);
-
- void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy);
-
- void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy, float velX, float velY);
-
- void onClick(@NonNull FloatingTaskView v);
- }
-
- public FloatingTaskLayer(Context context,
- FloatingTasksController controller,
- WindowManager windowManager) {
- super(context);
- // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly.
- setBackgroundColor(Color.argb(0, 0, 0, 0));
-
- mController = controller;
- mWindowManager = windowManager;
- updateSizes();
-
- // TODO: Might make sense to put dismiss controller in the touch handler since that's the
- // main user of dismiss controller.
- mDismissController = new FloatingDismissController(context, mController, this);
- mTouchHandler = new TouchHandlerImpl();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener);
- setOnApplyWindowInsetsListener((view, windowInsets) -> {
- if (!windowInsets.equals(mWindowInsets)) {
- mWindowInsets = windowInsets;
- updateSizes();
- }
- return windowInsets;
- });
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener);
- }
-
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- mTouchableRegion.setEmpty();
- getTouchableRegion(mTouchableRegion);
- inoutInfo.touchableRegion.set(mTouchableRegion);
- }
-
- /** Adds a floating task to the layout. */
- public void addTask(FloatingTasksController.Task task) {
- if (task.floatingView == null) return;
-
- task.floatingView.setTouchHandler(mTouchHandler);
- addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y));
- updateTaskViewPosition(task.floatingView);
- }
-
- /** Animates the stashed state of the provided task, if it's part of the floating layer. */
- public void setStashed(FloatingTasksController.Task task, boolean shouldStash) {
- if (task.floatingView != null && task.floatingView.getParent() == this) {
- mTouchHandler.stashTaskView(task.floatingView, shouldStash);
- }
- }
-
- /** Removes all {@link FloatingTaskView} from the layout. */
- public void removeAllTaskViews() {
- int childCount = getChildCount();
- ArrayList<View> viewsToRemove = new ArrayList<>();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- viewsToRemove.add(getChildAt(i));
- }
- }
- for (View v : viewsToRemove) {
- removeView(v);
- }
- }
-
- /** Returns the number of task views in the layout. */
- public int getTaskViewCount() {
- int taskViewCount = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- taskViewCount++;
- }
- }
- return taskViewCount;
- }
-
- /**
- * Called when the task view is un-stuck from the dismiss target.
- * @param v the task view being moved.
- * @param velX the x velocity of the motion event.
- * @param velY the y velocity of the motion event.
- * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there
- * was an 'up' event), otherwise the user is still dragging.
- */
- public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
- boolean wasFlungOut) {
- mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut);
- }
-
- /**
- * Updates dimensions and applies them to any task views.
- */
- public void updateSizes() {
- if (mDismissController != null) {
- mDismissController.updateSizes();
- }
-
- mOverhangWhenStashed = getResources().getDimensionPixelSize(
- R.dimen.floating_task_stash_offset);
- mVerticalPadding = getResources().getDimensionPixelSize(
- R.dimen.floating_task_vertical_padding);
-
- WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- WindowInsets windowInsets = windowMetrics.getWindowInsets();
- Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
- | WindowInsets.Type.statusBars()
- | WindowInsets.Type.displayCutout());
- Rect bounds = windowMetrics.getBounds();
- mPositionRect.set(bounds.left + insets.left,
- bounds.top + insets.top + mVerticalPadding,
- bounds.right - insets.right,
- bounds.bottom - insets.bottom - mVerticalPadding);
-
- int taskViewWidth = Math.max(bounds.height(), bounds.width());
- int taskViewHeight = Math.min(bounds.height(), bounds.width());
- taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2));
- mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight);
- mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top);
-
- // Update existing views
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- FloatingTaskView child = (FloatingTaskView) getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.width = mTaskViewSize.x;
- lp.height = mTaskViewSize.y;
- child.setLayoutParams(lp);
- updateTaskViewPosition(child);
- }
- }
- }
-
- /** Returns the first floating task view in the layout. (Currently only ever 1 view). */
- @Nullable
- public FloatingTaskView getFirstTaskView() {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child instanceof FloatingTaskView) {
- return (FloatingTaskView) child;
- }
- }
- return null;
- }
-
- private void updateTaskViewPosition(FloatingTaskView floatingView) {
- Point lastPosition = mController.getLastPosition();
- if (lastPosition.x == -1 && lastPosition.y == -1) {
- floatingView.setX(mDefaultStartPosition.x);
- floatingView.setY(mDefaultStartPosition.y);
- } else {
- floatingView.setX(lastPosition.x);
- floatingView.setY(lastPosition.y);
- }
- if (mTouchHandler.isStashedPosition(floatingView)) {
- floatingView.setStashed(true);
- }
- floatingView.updateLocation();
- }
-
- /**
- * Updates the area of the screen that shouldn't allow the back gesture due to the placement
- * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would
- * un-stash the task view instead of performing the back gesture).
- */
- private void updateSystemGestureExclusion() {
- Rect excludeZone = mSystemGestureExclusionRects.get(0);
- FloatingTaskView floatingTaskView = getFirstTaskView();
- if (floatingTaskView != null && floatingTaskView.isStashed()) {
- excludeZone.set(floatingTaskView.getLeft(),
- floatingTaskView.getTop(),
- floatingTaskView.getRight(),
- floatingTaskView.getBottom());
- excludeZone.offset((int) (floatingTaskView.getTranslationX()),
- (int) (floatingTaskView.getTranslationY()));
- setSystemGestureExclusionRects(mSystemGestureExclusionRects);
- } else {
- excludeZone.setEmpty();
- setSystemGestureExclusionRects(Collections.emptyList());
- }
- }
-
- /**
- * Fills in the touchable region for floating windows. This is used by WindowManager to
- * decide which touch events go to the floating windows.
- */
- private void getTouchableRegion(Region outRegion) {
- int childCount = getChildCount();
- Rect temp = new Rect();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child instanceof FloatingTaskView) {
- child.getBoundsOnScreen(temp);
- outRegion.op(temp, Region.Op.UNION);
- }
- }
- }
-
- /**
- * Implementation of the touch handler. Animates the task view based on touch events.
- */
- private class TouchHandlerImpl implements FloatingTaskTouchHandler {
- /**
- * The view can be stashed by swiping it towards the current edge or moving it there. If
- * the view gets moved in a way that is not one of these gestures, this is flipped to false.
- */
- private boolean mCanStash = true;
- /**
- * This is used to indicate that the view has been un-stuck from the dismiss target and
- * needs to spring to the current touch location.
- */
- // TODO: implement this behavior
- private boolean mSpringToTouchOnNextMotionEvent = false;
-
- private ArrayList<FlingAnimation> mFlingAnimations;
- private ViewPropertyAnimator mViewPropertyAnimation;
-
- private float mViewInitialX;
- private float mViewInitialY;
-
- private float[] mMinMax = new float[2];
-
- @Override
- public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY) {
- mCanStash = true;
- mViewInitialX = viewInitialX;
- mViewInitialY = viewInitialY;
- mDismissController.setUpMagneticObject(v);
- mDismissController.passEventToMagnetizedObject(ev);
- }
-
- @Override
- public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy) {
- // Shows the magnetic dismiss target if needed.
- mDismissController.showDismiss(/* show= */ true);
-
- // Send it to magnetic target first.
- if (mDismissController.passEventToMagnetizedObject(ev)) {
- v.setStashed(false);
- mCanStash = true;
-
- return;
- }
-
- // If we're here magnetic target didn't want it so move as per normal.
-
- v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true));
- v.setTranslationY(capY(v, mViewInitialY + dy));
- if (v.isStashed()) {
- // Check if we've moved far enough to be not stashed.
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX;
- if (viewInitiallyOnLeftSide) {
- if (v.getTranslationX() > mPositionRect.left) {
- v.setStashed(false);
- mCanStash = true;
- }
- } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) {
- v.setStashed(false);
- mCanStash = true;
- }
- }
- }
-
- // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge.
- // TODO clean up the code here, pretty hard to comprehend
- // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges)
- @Override
- public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy, float velX, float velY) {
-
- // Send it to magnetic target first.
- if (mDismissController.passEventToMagnetizedObject(ev)) {
- v.setStashed(false);
- return;
- }
- mDismissController.showDismiss(/* show= */ false);
-
- // If we're here magnetic target didn't want it so handle up as per normal.
-
- final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false);
- final float centerX = mPositionRect.centerX();
- final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX;
- final boolean viewOnLeftSide = x + v.getWidth() < centerX;
- final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY;
- final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY;
- // TODO: check velX here sometimes it doesn't stash on move when I think it should
- final boolean shouldStashFromMove =
- (velX < 0 && v.getTranslationX() < mPositionRect.left)
- || (velX > 0
- && v.getTranslationX() + v.getWidth() > mPositionRect.right);
- final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide
- && isFling
- && ((viewOnLeftSide && velX < ESCAPE_VELOCITY)
- || (!viewOnLeftSide && velX > ESCAPE_VELOCITY));
- final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove);
-
- ProtoLog.d(WM_SHELL_FLOATING_APPS,
- "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s"
- + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f"
- + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove,
- viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed());
-
- if (v.isStashed()) {
- mMinMax[0] = viewOnLeftSide
- ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
- : mPositionRect.right - v.getWidth();
- mMinMax[1] = viewOnLeftSide
- ? mPositionRect.left
- : mPositionRect.right - mOverhangWhenStashed;
- } else {
- populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax);
- }
-
- boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide;
- float destinationRelativeX = movingLeft
- ? mMinMax[0]
- : mMinMax[1];
-
- // TODO: why is this necessary / when does this happen?
- if (mMinMax[1] < v.getTranslationX()) {
- mMinMax[1] = v.getTranslationX();
- }
- if (v.getTranslationX() < mMinMax[0]) {
- mMinMax[0] = v.getTranslationX();
- }
-
- // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
- // so that it'll make it all the way to the side of the screen.
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- final float startXVelocity = movingLeft
- ? Math.min(minimumVelocityToReachEdge, velX)
- : Math.max(minimumVelocityToReachEdge, velX);
-
- cancelAnyAnimations(v);
-
- mFlingAnimations = getAnimationForUpEvent(v, shouldStash,
- startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX);
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- mFlingAnimations.get(i).start();
- }
- }
-
- @Override
- public void onClick(@NonNull FloatingTaskView v) {
- if (v.isStashed()) {
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewOnLeftSide = v.getTranslationX() < centerX;
- final float destinationRelativeX = viewOnLeftSide
- ? mPositionRect.left
- : mPositionRect.right - v.getWidth();
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax);
-
- cancelAnyAnimations(v);
-
- FlingAnimation flingAnimation = new FlingAnimation(v,
- DynamicAnimation.TRANSLATION_X);
- flingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(minimumVelocityToReachEdge)
- .setMinValue(mMinMax[0])
- .setMaxValue(mMinMax[1])
- .addEndListener((animation, canceled, value, velocity) -> {
- if (canceled) return;
- mController.setLastPosition((int) v.getTranslationX(),
- (int) v.getTranslationY());
- v.setStashed(false);
- v.updateLocation();
- });
- mFlingAnimations = new ArrayList<>();
- mFlingAnimations.add(flingAnimation);
- flingAnimation.start();
- }
- }
-
- public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
- boolean wasFlungOut) {
- if (wasFlungOut) {
- snapTaskViewToEdge(v, velX, /* shouldStash= */ false);
- } else {
- // TODO: use this for something / to spring the view to the touch location
- mSpringToTouchOnNextMotionEvent = true;
- }
- }
-
- public void stashTaskView(FloatingTaskView v, boolean shouldStash) {
- if (v.isStashed() == shouldStash) {
- return;
- }
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewOnLeftSide = v.getTranslationX() < centerX;
- snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash);
- }
-
- public boolean isStashedPosition(View v) {
- return v.getTranslationX() < mPositionRect.left
- || v.getTranslationX() + v.getWidth() > mPositionRect.right;
- }
-
- // TODO: a lot of this is duplicated in onUp -- can it be unified?
- private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) {
- final boolean movingLeft = velX < ESCAPE_VELOCITY;
- populateMinMax(v, movingLeft, shouldStash, mMinMax);
- float destinationRelativeX = movingLeft
- ? mMinMax[0]
- : mMinMax[1];
-
- // TODO: why is this necessary / when does this happen?
- if (mMinMax[1] < v.getTranslationX()) {
- mMinMax[1] = v.getTranslationX();
- }
- if (v.getTranslationX() < mMinMax[0]) {
- mMinMax[0] = v.getTranslationX();
- }
-
- // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
- // so that it'll make it all the way to the side of the screen.
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- final float startXVelocity = movingLeft
- ? Math.min(minimumVelocityToReachEdge, velX)
- : Math.max(minimumVelocityToReachEdge, velX);
-
- cancelAnyAnimations(v);
-
- mFlingAnimations = getAnimationForUpEvent(v,
- shouldStash, startXVelocity, mMinMax[0], mMinMax[1],
- destinationRelativeX);
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- mFlingAnimations.get(i).start();
- }
- }
-
- private void cancelAnyAnimations(FloatingTaskView v) {
- if (mFlingAnimations != null) {
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- if (mFlingAnimations.get(i).isRunning()) {
- mFlingAnimations.get(i).cancel();
- }
- }
- }
- if (mViewPropertyAnimation != null) {
- mViewPropertyAnimation.cancel();
- mViewPropertyAnimation = null;
- }
- }
-
- private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v,
- boolean shouldStash, float startVelX, float minValue, float maxValue,
- float destinationRelativeX) {
- final float ty = v.getTranslationY();
- final ArrayList<FlingAnimation> animations = new ArrayList<>();
- if (ty != capY(v, ty)) {
- // The view was being dismissed so the Y is out of bounds, need to animate that.
- FlingAnimation yFlingAnimation = new FlingAnimation(v,
- DynamicAnimation.TRANSLATION_Y);
- yFlingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(startVelX)
- .setMinValue(mPositionRect.top)
- .setMaxValue(mPositionRect.bottom - mTaskViewSize.y);
- animations.add(yFlingAnimation);
- }
- FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X);
- flingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(startVelX)
- .setMinValue(minValue)
- .setMaxValue(maxValue)
- .addEndListener((animation, canceled, value, velocity) -> {
- if (canceled) return;
- Runnable endAction = () -> {
- v.setStashed(shouldStash);
- v.updateLocation();
- if (!v.isStashed()) {
- mController.setLastPosition((int) v.getTranslationX(),
- (int) v.getTranslationY());
- }
- };
- if (!shouldStash) {
- final int xTranslation = (int) v.getTranslationX();
- if (xTranslation != destinationRelativeX) {
- // TODO: this animation doesn't feel great, should figure out
- // a better way to do this or remove the need for it all together.
- mViewPropertyAnimation = v.animate()
- .translationX(destinationRelativeX)
- .setListener(getAnimationListener(endAction));
- mViewPropertyAnimation.start();
- } else {
- endAction.run();
- }
- } else {
- endAction.run();
- }
- });
- animations.add(flingAnimation);
- return animations;
- }
-
- private AnimatorListenerAdapter getAnimationListener(Runnable endAction) {
- return new AnimatorListenerAdapter() {
- boolean translationCanceled = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- translationCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!translationCanceled) {
- endAction.run();
- }
- }
- };
- }
-
- private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash,
- float[] out) {
- if (shouldStash) {
- out[0] = onLeft
- ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
- : mPositionRect.right - v.getWidth();
- out[1] = onLeft
- ? mPositionRect.left
- : mPositionRect.right - mOverhangWhenStashed;
- } else {
- out[0] = mPositionRect.left;
- out[1] = mPositionRect.right - mTaskViewSize.x;
- }
- }
-
- private float getMinimumVelocityToReachEdge(FloatingTaskView v,
- float destinationRelativeX) {
- // Minimum velocity required for the view to make it to the targeted side of the screen,
- // taking friction into account (4.2f is the number that friction scalars are multiplied
- // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the
- // animation at the end will ensure that it reaches the destination X regardless.
- return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f);
- }
-
- private float capX(FloatingTaskView v, float x, boolean isMoving) {
- final int width = v.getWidth();
- if (v.isStashed() || isMoving) {
- if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) {
- return mPositionRect.left - v.getWidth() + mOverhangWhenStashed;
- }
- if (x > mPositionRect.right - mOverhangWhenStashed) {
- return mPositionRect.right - mOverhangWhenStashed;
- }
- } else {
- if (x < mPositionRect.left) {
- return mPositionRect.left;
- }
- if (x > mPositionRect.right - width) {
- return mPositionRect.right - width;
- }
- }
- return x;
- }
-
- private float capY(FloatingTaskView v, float y) {
- final int height = v.getHeight();
- if (y < mPositionRect.top) {
- return mPositionRect.top;
- }
- if (y > mPositionRect.bottom - height) {
- return mPositionRect.bottom - height;
- }
- return y;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
deleted file mode 100644
index 581204a82ec7..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
+++ /dev/null
@@ -1,385 +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.floating.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.RelativeTouchListener;
-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.floating.FloatingTasksController;
-
-/**
- * A view that holds a floating task using {@link TaskView} along with additional UI to manage
- * the task.
- */
-public class FloatingTaskView extends FrameLayout {
-
- private static final String TAG = FloatingTaskView.class.getSimpleName();
-
- private FloatingTasksController mController;
-
- private FloatingMenuView mMenuView;
- private int mMenuHeight;
- private TaskView mTaskView;
-
- private float mCornerRadius = 0f;
- private int mBackgroundColor;
-
- private FloatingTasksController.Task mTask;
-
- private boolean mIsStashed;
-
- /**
- * Creates a floating task view.
- *
- * @param context the context to use.
- * @param controller the controller to notify about changes in the floating task (e.g. removal).
- */
- public FloatingTaskView(Context context, FloatingTasksController controller) {
- super(context);
- mController = controller;
- setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation));
- mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size);
- mMenuView = new FloatingMenuView(context);
- addView(mMenuView);
-
- applyThemeAttrs();
-
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
- }
- });
- }
-
- // TODO: call this when theme/config changes
- void applyThemeAttrs() {
- boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources());
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
- android.R.attr.dialogCornerRadius,
- android.R.attr.colorBackgroundFloating});
- mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
- mCornerRadius = mCornerRadius / 2f;
- mBackgroundColor = ta.getColor(1, Color.WHITE);
-
- ta.recycle();
-
- mMenuView.setCornerRadius(mCornerRadius);
- mMenuHeight = getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_size);
-
- if (mTaskView != null) {
- mTaskView.setCornerRadius(mCornerRadius);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
-
- // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
- int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
- measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
-
- if (mTaskView != null) {
- int taskViewHeight = height - menuViewHeight;
- measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // Drag handle above
- final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
- mMenuView.layout(l, t, r, dragHandleBottom);
- if (mTaskView != null) {
- // Subtract radius so that the menu extends behind the rounded corners of TaskView.
- mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
- dragHandleBottom + mTaskView.getMeasuredHeight());
- }
- }
-
- /**
- * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work.
- */
- public void createTaskView(Context context, ShellTaskOrganizer organizer,
- TaskViewTransitions transitions, SyncTransactionQueue syncQueue) {
- mTaskView = new TaskView(context, organizer, transitions, syncQueue);
- addView(mTaskView);
- mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCornerRadius);
- }
-
- /**
- * Starts the provided task in the TaskView, if the TaskView exists. This should be called after
- * {@link #createTaskView}.
- */
- public void startTask(@ShellMainThread ShellExecutor executor,
- FloatingTasksController.Task task) {
- if (mTaskView == null) {
- Log.e(TAG, "starting task before creating the view!");
- return;
- }
- mTask = task;
- mTaskView.setListener(executor, mTaskViewListener);
- }
-
- /**
- * Sets the touch handler for the view.
- *
- * @param handler the touch handler for the view.
- */
- public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) {
- setOnTouchListener(new RelativeTouchListener() {
- @Override
- public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
- handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY());
- return true;
- }
-
- @Override
- public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy) {
- handler.onMove(FloatingTaskView.this, ev, dx, dy);
- }
-
- @Override
- public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy, float velX, float velY) {
- handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY);
- }
- });
- setOnClickListener(view -> {
- handler.onClick(FloatingTaskView.this);
- });
-
- mMenuView.addMenuItem(null, view -> {
- if (mIsStashed) {
- // If we're stashed all clicks un-stash.
- handler.onClick(FloatingTaskView.this);
- }
- });
- }
-
- private void setContentVisibility(boolean visible) {
- if (mTaskView == null) return;
- mTaskView.setAlpha(visible ? 1f : 0f);
- }
-
- /**
- * Sets the alpha of both this view and the TaskView.
- */
- public void setTaskViewAlpha(float alpha) {
- if (mTaskView != null) {
- mTaskView.setAlpha(alpha);
- }
- setAlpha(alpha);
- }
-
- /**
- * Call when the location or size of the view has changed to update TaskView.
- */
- public void updateLocation() {
- if (mTaskView == null) return;
- mTaskView.onLocationChanged();
- }
-
- private void updateMenuColor() {
- ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
- int color = info != null ? info.taskDescription.getBackgroundColor() : -1;
- if (color != -1) {
- mMenuView.setBackgroundColor(color);
- } else {
- mMenuView.setBackgroundColor(mBackgroundColor);
- }
- }
-
- /**
- * Sets whether the view is stashed or not.
- *
- * Also updates the touchable area based on this. If the view is stashed we don't direct taps
- * on the activity to the activity, instead a tap will un-stash the view.
- */
- public void setStashed(boolean isStashed) {
- if (mIsStashed != isStashed) {
- mIsStashed = isStashed;
- if (mTaskView == null) {
- return;
- }
- updateObscuredTouchRect();
- }
- }
-
- /** Whether the view is stashed at the edge of the screen or not. **/
- public boolean isStashed() {
- return mIsStashed;
- }
-
- private void updateObscuredTouchRect() {
- if (mIsStashed) {
- Rect tmpRect = new Rect();
- getBoundsOnScreen(tmpRect);
- mTaskView.setObscuredTouchRect(tmpRect);
- } else {
- mTaskView.setObscuredTouchRect(null);
- }
- }
-
- /**
- * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has
- * been called on this view or if
- * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called.
- */
- public boolean needsTaskStarted() {
- // If the task needs to be restarted then TaskView would have been cleaned up.
- return mTaskView == null;
- }
-
- /** Call this when the floating task activity is no longer in use. */
- public void cleanUpTaskView() {
- if (mTask != null && mTask.taskId != INVALID_TASK_ID) {
- try {
- ActivityTaskManager.getService().removeTask(mTask.taskId);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
- }
- }
- if (mTaskView != null) {
- mTaskView.release();
- removeView(mTaskView);
- mTaskView = null;
- }
- }
-
- // TODO: use task background colour / how to get the taskInfo ?
- private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getStatusBarColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
- }
-
- private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
- private boolean mInitialized = false;
- private boolean mDestroyed = false;
-
- @Override
- public void onInitialized() {
- if (mDestroyed || mInitialized) {
- return;
- }
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- /* enterResId= */ 0, /* exitResId= */ 0);
-
- Rect launchBounds = new Rect();
- mTaskView.getBoundsOnScreen(launchBounds);
-
- try {
- options.setTaskAlwaysOnTop(true);
- if (mTask.intent != null) {
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent,
- PendingIntent.FLAG_MUTABLE,
- null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
- } else {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent");
- }
- } catch (RuntimeException e) {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s",
- e.getMessage());
- mController.removeTask();
- }
- mInitialized = true;
- }
-
- @Override
- public void onReleased() {
- mDestroyed = true;
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName name) {
- mTask.taskId = taskId;
- updateMenuColor();
- setContentVisibility(true);
- }
-
- @Override
- public void onTaskVisibilityChanged(int taskId, boolean visible) {
- setContentVisibility(visible);
- }
-
- @Override
- public void onTaskRemovalStarted(int taskId) {
- // Must post because this is called from a binder thread.
- post(() -> {
- mController.removeTask();
- cleanUpTaskView();
- });
- }
-
- @Override
- public void onBackPressedOnTaskRoot(int taskId) {
- if (mTask.taskId == taskId && !mIsStashed) {
- // TODO: is removing the window the desired behavior?
- post(() -> mController.removeTask());
- }
- }
- };
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index e2d5a499d1e1..48487bc4a3d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -19,12 +19,8 @@ package com.android.wm.shell.freeform;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -41,31 +37,27 @@ import java.util.Optional;
/**
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
- *
- * @param <T> the type of window decoration instance
*/
-public class FreeformTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+ ShellTaskOrganizer.FocusListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
- private final WindowDecorViewModel<T> mWindowDecorationViewModel;
+ private final WindowDecorViewModel mWindowDecorationViewModel;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
public FreeformTaskListener(
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<T> windowDecorationViewModel) {
+ WindowDecorViewModel windowDecorationViewModel) {
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -76,96 +68,95 @@ public class FreeformTaskListener<T extends AutoCloseable>
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+ if (DesktopModeStatus.isAnyEnabled()) {
+ mShellTaskOrganizer.addFocusListener(this);
+ }
}
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mTasks.get(taskInfo.taskId) != null) {
+ throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+ final State state = new State();
+ state.mTaskInfo = taskInfo;
+ state.mLeash = leash;
+ mTasks.put(taskInfo.taskId, state);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
- if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- }
- }
-
- private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
+ if (DesktopModeStatus.isAnyEnabled()) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+ }
+ });
}
-
- state = new State<>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
}
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.IS_SUPPORTED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
+ if (DesktopModeStatus.isAnyEnabled()) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.removeFreeformTask(taskInfo.taskId);
+ if (repository.removeActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Removing active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+ });
}
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
}
- releaseWindowDecor(state.mWindowDecoration);
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = updateTaskInfo(taskInfo);
+ final State state = mTasks.get(taskInfo.taskId);
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
- }
-
- if (DesktopModeStatus.IS_SUPPORTED) {
- if (taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- }
+ mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
+ state.mTaskInfo = taskInfo;
+ if (DesktopModeStatus.isAnyEnabled()) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+ });
}
}
- private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
+ @Override
+ public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+ "Freeform Task Focus Changed: #%d focused=%b",
+ taskInfo.taskId, taskInfo.isFocused);
+ if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ });
}
- state.mTaskInfo = taskInfo;
- return state;
}
@Override
@@ -186,103 +177,6 @@ public class FreeformTaskListener<T extends AutoCloseable>
return mTasks.get(taskId).mLeash;
}
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if it creates the window decoration; {@code false} otherwise
- */
- boolean createWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (state.mWindowDecoration != null) {
- return false;
- }
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return true;
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- T giveWindowDecoration(
- RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (windowDecor == null) {
- return null;
- }
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- return windowDecor;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return false;
- }
- }
-
- void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- private void releaseWindowDecor(T windowDecor) {
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
- }
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index fd4c85fad77f..04fc79acadbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -46,14 +46,14 @@ public class FreeformTaskTransitionHandler
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
private final Transitions mTransitions;
- private final WindowDecorViewModel<?> mWindowDecorViewModel;
+ private final WindowDecorViewModel mWindowDecorViewModel;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
public FreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 17d60671e964..60e5ff27cab9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -16,13 +16,9 @@
package com.android.wm.shell.freeform;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
import android.app.ActivityManager;
import android.content.Context;
import android.os.IBinder;
-import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -31,9 +27,9 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,23 +43,19 @@ import java.util.Map;
* be a part of transitions.
*/
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
- private static final String TAG = "FreeformTO";
-
private final Transitions mTransitions;
- private final FreeformTaskListener<?> mFreeformTaskListener;
- private final FullscreenTaskListener<?> mFullscreenTaskListener;
+ private final WindowDecorViewModel mWindowDecorViewModel;
- private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();
+ private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
+ new HashMap<>();
public FreeformTaskTransitionObserver(
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
- mFreeformTaskListener = freeformTaskListener;
- mFullscreenTaskListener = fullscreenTaskListener;
+ mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -80,7 +72,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
- final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+ final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
@@ -98,7 +90,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
// This logic relies on 2 assumptions: 1 is that child tasks will be visited before
// parents (due to how z-order works). 2 is that no non-tasks are interleaved
// between tasks (hierarchically).
- taskParents.add(change.getContainer());
+ taskParents.add(change.getParent());
}
if (taskParents.contains(change.getContainer())) {
continue;
@@ -106,96 +98,53 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
switch (change.getMode()) {
case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
onOpenTransitionReady(change, startT, finishT);
break;
+ case WindowManager.TRANSIT_TO_FRONT:
+ onToFrontTransitionReady(change, startT, finishT);
+ break;
case WindowManager.TRANSIT_CLOSE: {
- onCloseTransitionReady(change, windowDecors, startT, finishT);
+ taskInfoList.add(change.getTaskInfo());
+ onCloseTransitionReady(change, startT, finishT);
break;
}
case WindowManager.TRANSIT_CHANGE:
- onChangeTransitionReady(info.getType(), change, startT, finishT);
+ onChangeTransitionReady(change, startT, finishT);
break;
}
}
- if (!windowDecors.isEmpty()) {
- mTransitionToWindowDecors.put(transition, windowDecors);
- }
+ mTransitionToTaskInfo.put(transition, taskInfoList);
}
private void onOpenTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- switch (change.getTaskInfo().getWindowingMode()){
- case WINDOWING_MODE_FREEFORM:
- mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- }
+ mWindowDecorViewModel.onTaskOpening(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onCloseTransitionReady(
TransitionInfo.Change change,
- ArrayList<AutoCloseable> windowDecors,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- final AutoCloseable windowDecor;
- switch (change.getTaskInfo().getWindowingMode()) {
- case WINDOWING_MODE_FREEFORM:
- windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- default:
- windowDecor = null;
- }
- if (windowDecor != null) {
- windowDecors.add(windowDecor);
- }
+ mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
}
private void onChangeTransitionReady(
- int type,
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- AutoCloseable windowDecor = null;
-
- boolean adopted = false;
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- windowDecor = mFreeformTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFullscreenTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
-
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFreeformTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
+ }
- if (!adopted) {
- releaseWindowDecor(windowDecor);
- }
+ private void onToFrontTransitionReady(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Override
@@ -203,43 +152,32 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
- if (windowDecorsOfMerged == null) {
+ final List<ActivityManager.RunningTaskInfo> infoOfMerged =
+ mTransitionToTaskInfo.get(merged);
+ if (infoOfMerged == null) {
// We are adding window decorations of the merged transition to them of the playing
// transition so if there is none of them there is nothing to do.
return;
}
- mTransitionToWindowDecors.remove(merged);
+ mTransitionToTaskInfo.remove(merged);
- final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
- if (windowDecorsOfPlaying != null) {
- windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
+ final List<ActivityManager.RunningTaskInfo> infoOfPlaying =
+ mTransitionToTaskInfo.get(playing);
+ if (infoOfPlaying != null) {
+ infoOfPlaying.addAll(infoOfMerged);
} else {
- mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
+ mTransitionToTaskInfo.put(playing, infoOfMerged);
}
}
@Override
public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
- final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
- transition, Collections.emptyList());
- mTransitionToWindowDecors.remove(transition);
-
- for (AutoCloseable windowDecor : windowDecors) {
- releaseWindowDecor(windowDecor);
- }
- mFullscreenTaskListener.onTaskTransitionFinished();
- mFreeformTaskListener.onTaskTransitionFinished();
- }
+ final List<ActivityManager.RunningTaskInfo> taskInfo =
+ mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
+ mTransitionToTaskInfo.remove(transition);
- private static void releaseWindowDecor(AutoCloseable windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ for (int i = 0; i < taskInfo.size(); ++i) {
+ mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 000000000000..0c2d5c49f830
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 76e296bb8c61..b9caf62012a6 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
@@ -22,13 +22,10 @@ import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Point;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,23 +43,20 @@ import java.util.Optional;
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
* @param <T> the type of window decoration instance
*/
-public class FullscreenTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
private final SyncTransactionQueue mSyncQueue;
private final Optional<RecentTasksController> mRecentTasksOptional;
- private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
/**
* This constructor is used by downstream products.
*/
@@ -75,7 +69,7 @@ public class FullscreenTaskListener<T extends AutoCloseable>
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
mShellTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mRecentTasksOptional = recentTasksOptional;
@@ -98,21 +92,21 @@ public class FullscreenTaskListener<T extends AutoCloseable>
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
- final State<T> state = new State();
+ final State state = new State();
state.mLeash = leash;
state.mTaskInfo = taskInfo;
mTasks.put(taskInfo.taskId, state);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
+ boolean createdWindowDecor = false;
if (mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
- leash, t, t);
+ createdWindowDecor = mWindowDecorViewModelOptional.get()
+ .onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
- if (state.mWindowDecoration == null) {
+ if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
@@ -127,13 +121,13 @@ public class FullscreenTaskListener<T extends AutoCloseable>
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
+ final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
- state.mTaskInfo = taskInfo;
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().onTaskInfoChanged(
- state.mTaskInfo, state.mWindowDecoration);
+
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
}
+ state.mTaskInfo = taskInfo;
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -147,160 +141,13 @@ public class FullscreenTaskListener<T extends AutoCloseable>
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
- }
- releaseWindowDecor(state.mWindowDecoration);
- }
-
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if a decoration was actually created.
- */
- public boolean createWindowDecoration(TransitionInfo.Change change,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (!mWindowDecorViewModelOptional.isPresent()) return false;
- if (state.mWindowDecoration != null) {
- // Already has a decoration.
- return false;
- }
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- return true;
- }
- return false;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- public boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- if (!mWindowDecorViewModelOptional.isPresent()) {
- return false;
- }
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
- windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- }
- return false;
- }
- }
-
- /**
- * Clear window decors of vanished tasks.
- */
- public void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- public T giveWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- }
-
- return windowDecor;
- }
-
- private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
- }
-
- state = new State<T>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
- }
-
- private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- state.mTaskInfo = taskInfo;
- return state;
- }
-
- private void releaseWindowDecor(T windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
}
}
@@ -342,6 +189,4 @@ public class FullscreenTaskListener<T extends AutoCloseable>
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
-
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165a78dc..2ee334873780 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@ public interface OneHanded {
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
/**
- * Returns a binder that can be passed to an external process to manipulate OneHanded.
- */
- default IOneHanded createExternalInterface() {
- return null;
- }
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0c4fe8c4fba..679d4ca2ac48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -24,6 +24,7 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -49,6 +50,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ this::createExternalInterface, this);
}
public OneHanded asOneHanded() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IOneHandedImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -709,17 +717,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
*/
@ExternalThread
private class OneHandedImpl implements OneHanded {
- private IOneHandedImpl mIOneHanded;
-
- @Override
- public IOneHanded createExternalInterface() {
- if (mIOneHanded != null) {
- mIOneHanded.invalidate();
- }
- mIOneHanded = new IOneHandedImpl(OneHandedController.this);
- return mIOneHanded;
- }
-
@Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IOneHandedImpl extends IOneHanded.Stub {
+ private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 4def15db2f52..2624ee536b58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -59,10 +59,15 @@ interface IPip {
/**
* Sets listener to get pinned stack animation callbacks.
*/
- oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+ oneway void setPipAnimationListener(IPipAnimationListener listener) = 3;
/**
* Sets the shelf height and visibility.
*/
oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+
+ /**
+ * Sets the next pip animation type to be the alpha animation.
+ */
+ oneway void setPipAnimationTypeToAlpha() = 5;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index c06881ae6ad7..f34d2a827e69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@ import java.util.function.Consumer;
*/
@ExternalThread
public interface Pip {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate PIP.
- */
- default IPip createExternalInterface() {
- return null;
- }
-
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
@@ -51,15 +43,6 @@ public interface Pip {
}
/**
- * Sets both shelf visibility and its height.
- *
- * @param visible visibility of shelf.
- * @param height to specify the height for shelf.
- */
- default void setShelfHeight(boolean visible, int height) {
- }
-
- /**
* Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
*
* @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
@@ -68,14 +51,6 @@ public interface Pip {
default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
/**
- * Set the pinned stack with {@link PipAnimationController.AnimationType}
- *
- * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
- */
- default void setPinnedStackAnimationType(int animationType) {
- }
-
- /**
* Called when showing Pip menu.
*/
default void showPictureInPictureMenu() {}
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 17d7f5d0d567..5376ae372de2 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
@@ -97,6 +97,8 @@ public class PipBoundsState {
private int mShelfHeight;
/** Whether the user has resized the PIP manually. */
private boolean mHasUserResizedPip;
+ /** Whether the user has moved the PIP manually. */
+ private boolean mHasUserMovedPip;
/**
* Areas defined by currently visible apps that they prefer to keep clear from overlays such as
* the PiP. Restricted areas may only move the PiP a limited amount from its anchor position.
@@ -279,6 +281,7 @@ public class PipBoundsState {
if (changed) {
clearReentryState();
setHasUserResizedPip(false);
+ setHasUserMovedPip(false);
}
}
@@ -442,6 +445,16 @@ public class PipBoundsState {
mHasUserResizedPip = hasUserResizedPip;
}
+ /** Returns whether the user has moved the PIP. */
+ public boolean hasUserMovedPip() {
+ return mHasUserMovedPip;
+ }
+
+ /** Set whether the user has moved the PIP. */
+ public void setHasUserMovedPip(boolean hasUserMovedPip) {
+ mHasUserMovedPip = hasUserMovedPip;
+ }
+
/**
* Registers a callback when the minimal size of PIP that is set by the app changes.
*/
@@ -577,6 +590,8 @@ public class PipBoundsState {
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
+ pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
if (mPipReentryState == null) {
pw.println(innerPrefix + "mPipReentryState=null");
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b9746e338ced..cbed4b5a501f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -115,8 +115,8 @@ public class PipSurfaceTransactionHelper {
// coordinates so offset the bounds to 0,0
mTmpDestinationRect.offsetTo(0, 0);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
@@ -129,9 +129,8 @@ public class PipSurfaceTransactionHelper {
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
} else {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
}
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
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 f170e774739f..8ba2583757cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -179,8 +180,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +199,39 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+ * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+ * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+ * the WCT should be the last transaction to finish the animation. However, it may happen that
+ * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+ * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+ * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+ * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+ * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+ *
+ * To avoid this, we delay the finishResize operation until
+ * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+
+ // Delay the finishResize to the next frame
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+ mMainExecutor.execute(finishResizeRunnable);
+ }, null);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1259,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1301,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
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 33761d23379d..e6c7e101d078 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
@@ -335,6 +335,7 @@ public class PipTransition extends PipTransitionController {
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+ mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
new Rect(mExitDestinationBounds), Surface.ROTATION_0);
}
mExitDestinationBounds.setEmpty();
@@ -357,8 +358,10 @@ public class PipTransition extends PipTransitionController {
WindowContainerTransaction wct = null;
if (isOutPipDirection(direction)) {
// Only need to reset surface properties. The server-side operations were already
- // done at the start.
- if (tx != null) {
+ // done at the start. But if it is running fixed rotation, there will be a seamless
+ // display transition later. So the last rotation transform needs to be kept to
+ // avoid flickering, and then the display transition will reset the transform.
+ if (tx != null && !mInFixedRotation) {
mFinishTransaction.merge(tx);
}
} else {
@@ -452,14 +455,17 @@ public class PipTransition extends PipTransitionController {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
TransitionInfo.Change pipChange = pipTaskChange;
- if (pipChange == null) {
+ if (mCurrentPipTaskToken == null) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
+ } else if (pipChange == null) {
// The pipTaskChange is null, this can happen if we are reparenting the PIP activity
// back to its original Task. In that case, we should animate the activity leash
- // instead, which should be the only non-task, independent, TRANSIT_CHANGE window.
+ // instead, which should be the change whose last parent is the recorded PiP Task.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null && change.getMode() == TRANSIT_CHANGE
- && TransitionInfo.isIndependent(change, info)) {
+ if (mCurrentPipTaskToken.equals(change.getLastParent())) {
+ // Find the activity that is exiting PiP.
pipChange = change;
break;
}
@@ -472,6 +478,20 @@ public class PipTransition extends PipTransitionController {
taskInfo);
return;
}
+
+ // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which
+ // case it may not be in the screen coordinate.
+ // 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());
+ 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 Rect currentBounds = mPipBoundsState.getBounds();
+ currentBounds.offset(-offset.x, -offset.y);
+ startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
+
mFinishCallback = (wct, wctCB) -> {
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(wct, wctCB);
@@ -493,18 +513,17 @@ public class PipTransition extends PipTransitionController {
if (displayRotationChange != null) {
// Exiting PIP to fullscreen with orientation change.
startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
- displayRotationChange, taskInfo, pipChange);
+ displayRotationChange, taskInfo, pipChange, offset);
return;
}
}
// Set the initial frame as scaling the end to the start.
final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
- final Point offset = pipChange.getEndRelOffset();
destinationBounds.offset(-offset.x, -offset.y);
- startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds);
- mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(),
- destinationBounds, mPipBoundsState.getBounds());
+ startTransaction.setWindowCrop(pipLeash, destinationBounds);
+ mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds,
+ currentBounds);
startTransaction.apply();
// Check if it is fixed rotation.
@@ -529,19 +548,21 @@ public class PipTransition extends PipTransitionController {
y = destinationBounds.bottom;
}
mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
- pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y,
+ pipLeash, endBounds, endBounds, new Rect(), degree, x, y,
true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
} else {
rotationDelta = Surface.ROTATION_0;
}
- startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta);
+ startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds,
+ rotationDelta);
}
private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionInfo.Change displayRotationChange,
- @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) {
+ @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange,
+ @NonNull Point offset) {
final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
displayRotationChange.getEndRotation());
@@ -553,7 +574,6 @@ public class PipTransition extends PipTransitionController {
final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
- final Point offset = pipChange.getEndRelOffset();
startBounds.offset(-offset.x, -offset.y);
endBounds.offset(-offset.x, -offset.y);
@@ -589,11 +609,12 @@ public class PipTransition extends PipTransitionController {
}
private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final Rect destinationBounds, final int rotationDelta) {
+ final Rect baseBounds, final Rect startBounds, final Rect endBounds,
+ final int rotationDelta) {
final PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
- mPipBoundsState.getBounds(), destinationBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta);
+ mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
+ endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP,
+ 0 /* startingAngle */, rotationDelta);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 84071e08d472..690505e03fce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Gravity;
@@ -34,6 +35,10 @@ import java.util.Set;
*/
public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+ private boolean mKeepClearAreaGravityEnabled =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
+
protected int mKeepClearAreasPadding;
public PhonePipKeepClearAlgorithm(Context context) {
@@ -53,31 +58,36 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
Rect startingBounds = pipBoundsState.getBounds().isEmpty()
? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
: pipBoundsState.getBounds();
- float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
- int verticalGravity = Gravity.BOTTOM;
- int horizontalGravity;
- if (snapFraction >= 0.5f && snapFraction < 2.5f) {
- horizontalGravity = Gravity.RIGHT;
- } else {
- horizontalGravity = Gravity.LEFT;
- }
- // push the bounds based on the gravity
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
insets.bottom -= pipBoundsState.getImeHeight();
}
- Rect pushedBounds = new Rect(startingBounds);
- if (verticalGravity == Gravity.BOTTOM) {
- pushedBounds.offsetTo(pushedBounds.left,
- insets.bottom - pushedBounds.height());
- }
- if (horizontalGravity == Gravity.RIGHT) {
- pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
- } else {
- pushedBounds.offsetTo(insets.left, pushedBounds.top);
+ Rect pipBounds = new Rect(startingBounds);
+
+ // move PiP towards corner if user hasn't moved it manually or the flag is on
+ if (mKeepClearAreaGravityEnabled
+ || (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
+ float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
+ int verticalGravity = Gravity.BOTTOM;
+ int horizontalGravity;
+ if (snapFraction >= 0.5f && snapFraction < 2.5f) {
+ horizontalGravity = Gravity.RIGHT;
+ } else {
+ horizontalGravity = Gravity.LEFT;
+ }
+ if (verticalGravity == Gravity.BOTTOM) {
+ pipBounds.offsetTo(pipBounds.left,
+ insets.bottom - pipBounds.height());
+ }
+ if (horizontalGravity == Gravity.RIGHT) {
+ pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top);
+ } else {
+ pipBounds.offsetTo(insets.left, pipBounds.top);
+ }
}
- return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
+
+ return findUnoccludedPosition(pipBounds, pipBoundsState.getRestrictedKeepClearAreas(),
pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
}
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 af47666efa5a..01d81ff4e436 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
@@ -23,6 +23,7 @@ import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
@@ -32,6 +33,7 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -67,6 +69,7 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -158,6 +161,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the keep clear areas feature is disabled
return;
}
+ if (mPipBoundsState.isStashed()) {
+ // don't move when stashed
+ return;
+ }
// if there is another animation ongoing, wait for it to finish and try again
if (mPipAnimationController.isAnimating()) {
mMainExecutor.removeCallbacks(
@@ -200,7 +207,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private Consumer<Boolean> mOnIsInPipStateChangedListener;
- private interface PipAnimationListener {
+ @VisibleForTesting
+ interface PipAnimationListener {
/**
* Notifies the listener that the Pip animation is started.
*/
@@ -427,11 +435,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
- // Ensure that we are the primary user's SystemUI.
- final int processUser = UserManager.get(context).getProcessUserId();
- if (processUser != UserHandle.USER_SYSTEM) {
- throw new IllegalStateException("Non-primary Pip component not currently supported.");
- }
+
mContext = context;
mShellCommandHandler = shellCommandHandler;
@@ -605,9 +609,33 @@ public class PipController implements PipTransitionController.PipTransitionCallb
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ DisplayLayout pendingLayout =
+ mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ if (mIsInFixedRotation
+ || pendingLayout.rotation()
+ != mPipBoundsState.getDisplayLayout().rotation()) {
+ // bail out if there is a pending rotation or fixed rotation change
+ return;
+ }
+ int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
+ int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+ if (!mEnablePipKeepClearAlgorithm) {
+ // offset PiP to adjust for bottom inset change
+ int pipTop = mPipBoundsState.getBounds().top;
+ int diff = newMaxMovementBound - oldMaxMovementBound;
+ if (diff < 0 && pipTop > newMaxMovementBound) {
+ // bottom inset has increased, move PiP up if it is too low
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+ newMaxMovementBound - pipTop);
+ }
+ if (diff > 0 && oldMaxMovementBound == pipTop) {
+ // bottom inset has decreased, move PiP down if it was by the edge
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+ }
+ }
}
});
@@ -631,6 +659,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
}
@Override
@@ -732,6 +766,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Directly move PiP to its final destination bounds without animation.
mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
+
+ // if the pip window size is beyond allowed bounds user resize to normal bounds
+ if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
+ || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
+ || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y
+ || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) {
+ mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction);
+ }
+
} else {
updateDisplayLayout.run();
}
@@ -826,11 +869,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb
animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
}
- private void setPinnedStackAnimationListener(PipAnimationListener callback) {
+ @VisibleForTesting
+ void setPinnedStackAnimationListener(PipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
onPipResourceDimensionsChanged();
}
+ @VisibleForTesting
+ boolean hasPinnedStackAnimationListener() {
+ return mPinnedStackAnimationRecentsCallback != null;
+ }
+
private void onPipResourceDimensionsChanged() {
if (mPinnedStackAnimationRecentsCallback != null) {
mPinnedStackAnimationRecentsCallback.onPipResourceDimensionsChanged(
@@ -1039,17 +1088,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* The interface for calls from outside the Shell, within the host process.
*/
private class PipImpl implements Pip {
- private IPipImpl mIPip;
-
- @Override
- public IPip createExternalInterface() {
- if (mIPip != null) {
- mIPip.invalidate();
- }
- mIPip = new IPipImpl(PipController.this);
- return mIPip;
- }
-
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1065,13 +1103,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setShelfHeight(boolean visible, int height) {
- mMainExecutor.execute(() -> {
- PipController.this.setShelfHeight(visible, height);
- });
- }
-
- @Override
public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
PipController.this.setOnIsInPipStateChangedListener(callback);
@@ -1079,13 +1110,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setPinnedStackAnimationType(int animationType) {
- mMainExecutor.execute(() -> {
- PipController.this.setPinnedStackAnimationType(animationType);
- });
- }
-
- @Override
public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
mMainExecutor.execute(() -> {
mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
@@ -1111,7 +1135,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IPipImpl extends IPip.Stub {
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
private final SingleInstanceRemoteListener<PipController,
IPipAnimationListener> mListener;
@@ -1142,8 +1166,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
+ // Unregister the listener to ensure any registered binder death recipients are unlinked
+ mListener.unregister();
}
@Override
@@ -1178,8 +1205,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
- executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
(controller) -> {
if (listener != null) {
mListener.register(listener);
@@ -1188,5 +1215,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
}
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {
+ executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
+ (controller) -> {
+ controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index afb64c9eec41..43d3f36f1fe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -60,7 +60,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
FloatingContentCoordinator.FloatingContent {
public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
- SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true);
+ SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false);
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
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 89d85e4b292d..41ff0b35a035 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
@@ -96,6 +96,7 @@ public class PipResizeGestureHandler {
private final Rect mDisplayBounds = new Rect();
private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
+ private final Consumer<Rect> mUpdateResizeBoundsCallback;
private int mDelta;
private float mTouchSlop;
@@ -137,6 +138,13 @@ public class PipResizeGestureHandler {
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mUpdateResizeBoundsCallback = (rect) -> {
+ mUserResizeBounds.set(rect);
+ mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
}
public void init() {
@@ -508,15 +516,50 @@ public class PipResizeGestureHandler {
}
}
+ private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
+ final int leftEdge = bounds.left;
+
+
+ final int fromLeft = Math.abs(leftEdge - movementBounds.left);
+ final int fromRight = Math.abs(movementBounds.right - leftEdge);
+
+ // The PIP will be snapped to either the right or left edge, so calculate which one
+ // is closest to the current position.
+ final int newLeft = fromLeft < fromRight
+ ? movementBounds.left : movementBounds.right;
+
+ bounds.offsetTo(newLeft, mLastResizeBounds.top);
+ }
+
+ /**
+ * Resizes the pip window and updates user-resized bounds.
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ Rect finalBounds = new Rect(bounds);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds);
+
+ // snap the target bounds to the either left or right edge, by choosing the closer one
+ snapToMovementBoundsEdge(finalBounds, movementBounds);
+
+ // apply the requested snap fraction onto the target bounds
+ mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction);
+
+ // resize from current bounds to target bounds without animation
+ mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null);
+ // set the flag that pip has been resized
+ mPipBoundsState.setHasUserResizedPip(true);
+
+ // finish the resize operation and update the state of the bounds
+ mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
+ }
+
private void finishResize() {
if (!mLastResizeBounds.isEmpty()) {
- final Consumer<Rect> callback = (rect) -> {
- mUserResizeBounds.set(mLastResizeBounds);
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
- resetState();
- };
-
// Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
// position correctly. Drag-resize does not need to move, so just finalize resize.
if (mOngoingPinchToResize) {
@@ -526,24 +569,23 @@ public class PipResizeGestureHandler {
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
}
- final int leftEdge = mLastResizeBounds.left;
- final Rect movementBounds =
- mPipBoundsAlgorithm.getMovementBounds(mLastResizeBounds);
- final int fromLeft = Math.abs(leftEdge - movementBounds.left);
- final int fromRight = Math.abs(movementBounds.right - leftEdge);
- // The PIP will be snapped to either the right or left edge, so calculate which one
- // is closest to the current position.
- final int newLeft = fromLeft < fromRight
- ? movementBounds.left : movementBounds.right;
- mLastResizeBounds.offsetTo(newLeft, mLastResizeBounds.top);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm
+ .getMovementBounds(mLastResizeBounds);
+
+ // snap mLastResizeBounds to the correct edge based on movement bounds
+ snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, callback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
+ PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
+ mUpdateResizeBoundsCallback);
}
final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
mPipDismissTargetHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 1f3f31e025a0..83bc7c0e6e7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@ public class PipTouchHandler {
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
+ if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
@@ -825,6 +825,16 @@ public class PipTouchHandler {
}
/**
+ * Resizes the pip window and updates user resized bounds
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+ }
+
+ /**
* Gesture controlling normal movement of the PIP.
*/
private class DefaultPipTouchGesture extends PipTouchGesture {
@@ -865,6 +875,8 @@ public class PipTouchHandler {
}
if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
// Move the pinned stack freely
final PointF lastDelta = touchState.getLastTouchDelta();
float lastX = mStartPosition.x + mDelta.x;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index c52ed249c2ca..75f9a4c33af9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -42,8 +42,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
- Consts.TAG_WM_SHELL),
+ WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SPLIT_SCREEN),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -110,6 +110,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
private static class Consts {
private static final String TAG_WM_SHELL = "WindowManagerShell";
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
+ private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde05274..93ffb3dc8115 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
package com.android.wm.shell.protolog;
import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.BaseProtoLogImpl;
import com.android.internal.protolog.ProtoLogViewerConfigReader;
import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
-import org.json.JSONException;
-
/**
* A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@ import org.json.JSONException;
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: Get the right path for the proto log file when we initialize the shell components
- private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+ // TODO: find a proper location to save the protolog message file
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+ private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
private static ShellProtoLogImpl sServiceInstance = null;
@@ -111,18 +104,8 @@ public class ShellProtoLogImpl extends BaseProtoLogImpl {
}
public int startTextLogging(String[] groups, PrintWriter pw) {
- try (InputStream is =
- getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
- mViewerConfig.loadViewerConfig(is);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- } catch (IOException e) {
- Log.i(TAG, "Unable to load log definitions: IOException while reading "
- + "wm_shell_protolog. " + e);
- } catch (JSONException e) {
- Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
- + "wm_shell_protolog. " + e);
- }
- return -1;
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(true /* setTextLogging */, true, pw, groups);
}
public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@ public class ShellProtoLogImpl extends BaseProtoLogImpl {
}
private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
}
}
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 b71cc32a0347..1a6c1d65db03 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
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -44,5 +44,5 @@ interface IRecentTasks {
/**
* Gets the set of running tasks.
*/
- ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+ RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 59f72335678e..e8f58fe2bfad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
@@ -31,10 +31,10 @@ oneway interface IRecentTasksListener {
/**
* Called when a running task appears.
*/
- void onRunningTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo);
+ void onRunningTaskAppeared(in RunningTaskInfo taskInfo);
/**
* Called when a running task vanishes.
*/
- void onRunningTaskVanished(in ActivityManager.RunningTaskInfo taskInfo);
-} \ No newline at end of file
+ void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a625524b48b..069066e4bd49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@ import java.util.function.Consumer;
@ExternalThread
public interface RecentTasks {
/**
- * Returns a binder that can be passed to an external process to fetch recent tasks.
- */
- default IRecentTasks createExternalInterface() {
- return null;
- }
-
- /**
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
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 02b5a35f653b..8490f9f156c7 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
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -65,15 +68,16 @@ import java.util.function.Consumer;
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener {
+ RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
- private final RecentTasks mImpl = new RecentTasksImpl();
+ private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+ taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,10 +138,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IRecentTasksImpl(this);
+ }
+
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
- mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
+ mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
}
/**
@@ -267,15 +280,22 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
- private void registerRecentTasksListener(IRecentTasksListener listener) {
+ @VisibleForTesting
+ void registerRecentTasksListener(IRecentTasksListener listener) {
mListener = listener;
}
- private void unregisterRecentTasksListener() {
+ @VisibleForTesting
+ void unregisterRecentTasksListener() {
mListener = null;
}
@VisibleForTesting
+ boolean hasRecentTasksListener() {
+ return mListener != null;
+ }
+
+ @VisibleForTesting
ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is from the most-recent to least-recent order
final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
@@ -329,6 +349,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
+ * Returns the top running leaf task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask() {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
+ false /* filterOnlyVisibleRecents */);
+ return tasks.isEmpty() ? null : tasks.get(0);
+ }
+
+ /**
* Find the background task that match the given component.
*/
@Nullable
@@ -354,6 +384,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(prefix + " mListener=" + mListener);
+ pw.println(prefix + "Tasks:");
ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
@@ -366,17 +398,6 @@ public class RecentTasksController implements TaskStackListenerCallback,
*/
@ExternalThread
private class RecentTasksImpl implements RecentTasks {
- private IRecentTasksImpl mIRecentTasks;
-
- @Override
- public IRecentTasks createExternalInterface() {
- if (mIRecentTasks != null) {
- mIRecentTasks.invalidate();
- }
- mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
- return mIRecentTasks;
- }
-
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +414,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IRecentTasksImpl extends IRecentTasks.Stub {
+ private static class IRecentTasksImpl extends IRecentTasks.Stub
+ implements ExternalInterfaceBinder {
private RecentTasksController mController;
private final SingleInstanceRemoteListener<RecentTasksController,
IRecentTasksListener> mListener;
@@ -424,8 +446,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
+ // Unregister the listener to ensure any registered binder death recipients are unlinked
+ mListener.unregister();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index ecdafa9a63f4..56aa742b8626 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -79,24 +79,59 @@ interface ISplitScreen {
/**
* Starts tasks simultaneously in one transition.
*/
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
+ oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ in InstanceId instanceId) = 10;
+
+ /**
+ * Starts a pair of intent and task in one transition.
+ */
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
+
+ /**
+ * Starts a pair of shortcut and task in one transition.
+ */
+ oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
+ oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
- int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
+
+ /**
+ * Starts a pair of shortcut and task using legacy transition system.
+ */
+ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
+
+ /**
+ * Start a pair of intents using legacy transition system.
+ */
+ oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+ in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in InstanceId instanceId) = 18;
+
+ /**
+ * Start a pair of intents in one transition.
+ */
+ oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+ in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
@@ -111,11 +146,5 @@ interface ISplitScreen {
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
-
- /**
- * Starts a pair of shortcut and task using legacy transition system.
- */
- oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
- in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
}
+// Last id = 19 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index e7ec15e70c11..89538cb394d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -16,9 +16,6 @@
package com.android.wm.shell.splitscreen;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
-
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -34,8 +31,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
* @see StageCoordinator
*/
class MainStage extends StageTaskListener {
- private static final String TAG = MainStage.class.getSimpleName();
-
private boolean mIsActive = false;
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
@@ -52,15 +47,8 @@ class MainStage extends StageTaskListener {
void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
- final WindowContainerToken rootToken = mRootTaskInfo.token;
if (includingTopTask) {
- wct.reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */,
- true /* reparentTopOnly */);
+ reparentTopTask(wct);
}
mIsActive = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799b7a3d..2f2bc77b804b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,16 +70,12 @@ public interface SplitScreen {
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
+ /** Called when requested to go to fullscreen from the current active split app. */
+ void goToFullscreenFromSplit();
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 07a6895e2720..38099fc51d81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -29,8 +29,11 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -54,23 +57,24 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.widget.Toast;
import android.window.RemoteTransition;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -93,7 +97,6 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -118,6 +121,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_SCREEN_LOCKED = 7;
public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_RECREATE_SPLIT = 10;
+ public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -129,6 +134,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
+ EXIT_REASON_RECREATE_SPLIT,
+ EXIT_REASON_FULLSCREEN_SHORTCUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -164,8 +171,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mAppsSupportMultiInstances;
+
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
- private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -208,12 +218,61 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mAppsSupportMultiInstances = mContext.getResources()
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mAppsSupportMultiInstances = mContext.getResources()
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new ISplitScreenImpl(this);
+ }
+
/**
* This will be called after ShellTaskOrganizer has initialized/registered because of the
* dependency order.
@@ -224,6 +283,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
@@ -256,10 +317,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator;
}
- public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mStageCoordinator.getFocusingTaskInfo();
- }
-
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
}
@@ -363,6 +420,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ public void goToFullscreenFromSplit() {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -411,7 +472,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
*/
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
- mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
+ mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
startShortcut(packageName, shortcutId, position, options, user);
}
@@ -459,76 +520,191 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
*/
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
- mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
+ mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameAppAdjacently(pendingIntent, taskId)) {
+ if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ try {
+ adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameAppAdjacently(pendingIntent, taskId)) {
+ if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) {
+ if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ try {
+ adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+ pendingIntent1.send();
+ } catch (RemoteException | PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+ pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameAppAdjacently(position, intent)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
+ return;
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
- /** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
-
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
- return false;
+ @Nullable
+ private String getPackageName(Intent intent) {
+ if (intent == null || intent.getComponent() == null) {
+ return null;
}
+ return intent.getComponent().getPackageName();
+ }
+ private boolean launchSameAppAdjacently(@SplitPosition int position,
+ PendingIntent pendingIntent) {
+ ActivityManager.RunningTaskInfo adjacentTaskInfo = null;
if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
+ adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position));
+ } else {
+ adjacentTaskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+ if (!isValidToEnterSplitScreen(adjacentTaskInfo)) {
return false;
}
+ }
+
+ if (adjacentTaskInfo == null) {
+ return false;
+ }
+
+ final String targetPackageName = getPackageName(pendingIntent.getIntent());
+ final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
+ return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+ }
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) {
+ final ActivityManager.RunningTaskInfo adjacentTaskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (adjacentTaskInfo == null) {
+ return false;
}
+ final String targetPackageName = getPackageName(pendingIntent.getIntent());
+ final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
+ return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+ }
+
+ private boolean launchSameAppAdjacently(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2) {
+ final String targetPackageName = getPackageName(pendingIntent1.getIntent());
+ final String adjacentPackageName = getPackageName(pendingIntent2.getIntent());
+ return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+ }
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String packageName = launching.getPackageName();
+ for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
+ if (mAppsSupportMultiInstances[i].equals(packageName)) {
+ return true;
+ }
}
return false;
@@ -610,10 +786,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return splitTasksLayer;
}
/**
- * Sets drag info to be logged when splitscreen is entered.
+ * Drop callback when splitscreen is entered.
*/
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
- mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.onDroppedToSplit(position, dragSessionId);
}
/**
@@ -641,6 +817,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
return "CHILD_TASK_ENTER_PIP";
+ case EXIT_REASON_RECREATE_SPLIT:
+ return "RECREATE_SPLIT";
default:
return "unknown reason, reason int = " + exitReason;
}
@@ -658,7 +836,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
@@ -704,15 +881,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
};
@Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -742,9 +910,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void onFinishedWakingUp() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedWakingUp();
- });
+ mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+ }
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
}
@@ -752,7 +923,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private static class ISplitScreenImpl extends ISplitScreen.Stub
+ implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
@@ -779,8 +951,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
+ // Unregister the listener to ensure any registered binder death recipients are unlinked
+ mListener.unregister();
}
@Override
@@ -798,97 +973,126 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
- });
+ (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
}
@Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
+ (controller) -> controller.removeFromSideStage(taskId));
}
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
+ (controller) -> controller.startTask(taskId, position, options));
}
@Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ taskId1, options1, taskId2, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
- int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
- sidePosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
- shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
+ shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio,
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+ (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
+ taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ instanceId));
+ }
+
+ @Override
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
+ }
+
+ @Override
+ public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
+ (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
+ options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
instanceId));
}
@Override
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+ (controller) ->
+ controller.startIntentsWithLegacyTransition(
+ pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId)
+ );
+ }
+
+ @Override
+ public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ // TODO(b/259368992): To be implemented.
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position, options, user,
- instanceId);
- });
+ (controller) -> controller.startShortcut(packageName, shortcutId, position,
+ options, user, instanceId));
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options, instanceId);
- });
+ (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+ instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d7ca791e3863..1cf3a896b68e 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
@@ -47,6 +47,7 @@ import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@ class SplitScreenTransitions {
DismissTransition mPendingDismiss = null;
TransitSession mPendingEnter = null;
TransitSession mPendingRecent = null;
+ TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -108,6 +110,14 @@ class SplitScreenTransitions {
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null && pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ t.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -169,10 +179,45 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
}
+ void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ mFinishTransaction = finishTransaction;
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+
+ SplitDecorManager decor = mainRoot.equals(change.getContainer())
+ ? mainDecor : sideDecor;
+ ValueAnimator va = new ValueAnimator();
+ mAnimations.add(va);
+ decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+ decor.onResized(startTransaction, () -> {
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ });
+ }
+ }
+
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ }
+
boolean isPendingTransition(IBinder transition) {
- return isPendingEnter(transition)
- || isPendingDismiss(transition)
- || isPendingRecent(transition);
+ return getPendingTransition(transition) != null;
}
boolean isPendingEnter(IBinder transition) {
@@ -187,22 +232,45 @@ class SplitScreenTransitions {
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ boolean isPendingResize(IBinder transition) {
+ return mPendingResize != null && mPendingResize.mTransition == transition;
+ }
+
+ @Nullable
+ private TransitSession getPendingTransition(IBinder transition) {
+ if (isPendingEnter(transition)) {
+ return mPendingEnter;
+ } else if (isPendingRecent(transition)) {
+ return mPendingRecent;
+ } else if (isPendingDismiss(transition)) {
+ return mPendingDismiss;
+ } else if (isPendingResize(transition)) {
+ return mPendingResize;
+ }
+
+ return null;
+ }
+
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionCallback callback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, callback);
+ setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingEnter = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
+ mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -236,9 +304,25 @@ class SplitScreenTransitions {
exitReasonToString(reason), stageTypeToString(dismissTop));
}
+ IBinder startResizeTransition(WindowContainerTransaction wct,
+ Transitions.TransitionHandler handler,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+ setResizeTransition(transition, finishCallback);
+ return transition;
+ }
+
+ void setResizeTransition(@NonNull IBinder transition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Resize split screen");
+ }
+
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingRecent = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -248,7 +332,7 @@ class SplitScreenTransitions {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
+ + " deduced Enter recent panel");
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -256,14 +340,9 @@ class SplitScreenTransitions {
if (mergeTarget != mAnimatingTransition) return;
if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- mPendingRecent.mCallback = new TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- }
- };
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ mPendingRecent.setFinishedCallback(null);
}
if (mActiveRemoteHandler != null) {
@@ -277,7 +356,7 @@ class SplitScreenTransitions {
}
boolean end() {
- // If its remote, there's nothing we can do right now.
+ // If It's remote, there's nothing we can do right now.
if (mActiveRemoteHandler != null) return false;
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
@@ -290,45 +369,44 @@ class SplitScreenTransitions {
@Nullable SurfaceControl.Transaction finishT) {
if (isPendingEnter(transition)) {
if (!aborted) {
- // An enter transition got merged, appends the rest operations to finish entering
+ // An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
mPendingRemoteHandler = null;
}
- mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
- mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
} else if (isPendingRecent(transition)) {
- mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
+ } else if (isPendingResize(transition)) {
+ mPendingResize.onConsumed(aborted);
+ mPendingResize = null;
}
}
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- TransitionCallback callback = null;
+ if (wct == null) wct = new WindowContainerTransaction();
if (isPendingEnter(mAnimatingTransition)) {
- callback = mPendingEnter.mCallback;
+ mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- }
- if (isPendingDismiss(mAnimatingTransition)) {
- callback = mPendingDismiss.mCallback;
- mPendingDismiss = null;
- }
- if (isPendingRecent(mAnimatingTransition)) {
- callback = mPendingRecent.mCallback;
+ } else if (isPendingRecent(mAnimatingTransition)) {
+ mPendingRecent.onFinished(wct, mFinishTransaction);
mPendingRecent = null;
- }
-
- if (callback != null) {
- if (wct == null) wct = new WindowContainerTransaction();
- callback.onTransitionFinished(wct, mFinishTransaction);
+ } else if (isPendingDismiss(mAnimatingTransition)) {
+ mPendingDismiss.onFinished(wct, mFinishTransaction);
+ mPendingDismiss = null;
+ } else if (isPendingResize(mAnimatingTransition)) {
+ mPendingResize.onFinished(wct, mFinishTransaction);
+ mPendingResize = null;
}
mPendingRemoteHandler = null;
@@ -363,10 +441,7 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
});
};
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
+ va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -376,9 +451,6 @@ class SplitScreenTransitions {
public void onAnimationCancel(Animator animation) {
finisher.run();
}
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
@@ -432,24 +504,66 @@ class SplitScreenTransitions {
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Clean-up callbacks for transition. */
- interface TransitionCallback {
- /** Calls when the transition got consumed. */
- default void onTransitionConsumed(boolean aborted) {}
+ /** Calls when the transition got consumed. */
+ interface TransitionConsumedCallback {
+ void onConsumed(boolean aborted);
+ }
- /** Calls when the transition finished. */
- default void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {}
+ /** Calls when the transition finished. */
+ interface TransitionFinishedCallback {
+ void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
- TransitionCallback mCallback;
+ TransitionConsumedCallback mConsumedCallback;
+ TransitionFinishedCallback mFinishedCallback;
- TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ /** Whether the transition was canceled. */
+ boolean mCanceled;
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
mTransition = transition;
- mCallback = callback != null ? callback : new TransitionCallback() {};
+ mConsumedCallback = consumedCallback;
+ mFinishedCallback = finishedCallback;
+
+ }
+
+ /** Sets transition consumed callback. */
+ void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
+ mConsumedCallback = callback;
+ }
+
+ /** Sets transition finished callback. */
+ void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
+ mFinishedCallback = callback;
+ }
+
+ /**
+ * Cancels the transition. This should be called before playing animation. A canceled
+ * transition will skip playing animation.
+ *
+ * @param finishedCb new finish callback to override.
+ */
+ void cancel(@Nullable TransitionFinishedCallback finishedCb) {
+ mCanceled = true;
+ setFinishedCallback(finishedCb);
+ }
+
+ void onConsumed(boolean aborted) {
+ if (mConsumedCallback != null) {
+ mConsumedCallback.onConsumed(aborted);
+ }
+ }
+
+ void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.onFinished(finishWct, finishT);
+ }
}
}
@@ -459,7 +573,7 @@ class SplitScreenTransitions {
final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- super(transition, null /* callback */);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a0441b06..5483fa5d29f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -21,8 +21,11 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -36,8 +39,11 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +186,12 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+ case EXIT_REASON_CHILD_TASK_ENTER_PIP:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP;
+ case EXIT_REASON_RECREATE_SPLIT:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c17f8226c925..8ddc3c04d991 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
@@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -53,6 +52,8 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
@@ -115,6 +116,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.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.TransactionPool;
@@ -150,7 +152,7 @@ import java.util.Optional;
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
+ ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -169,6 +171,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
private boolean mKeyguardShowing;
+ private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -185,8 +188,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
- private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
-
/**
* A single-top root task which the split divider attached to.
*/
@@ -199,14 +200,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mIsDividerRemoteAnimating;
+ private boolean mIsSplitEntering;
+ private boolean mIsDropEntering;
private boolean mIsExiting;
- /** The target stage to dismiss to when unlock after folded. */
- @StageType
- private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-
private DefaultMixedHandler mMixedHandler;
+ private final Toast mSplitUnsupportedToast;
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@@ -225,33 +224,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(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;
- }
- }
+ 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);
- }
- };
+ // 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,
@@ -299,7 +301,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
- mTaskOrganizer.addFocusListener(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@VisibleForTesting
@@ -329,6 +332,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -340,10 +345,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mSplitTransitions;
}
- boolean isSplitScreenVisible() {
+ public boolean isSplitScreenVisible() {
return mSideStageListener.mVisible && mMainStageListener.mVisible;
}
+ public boolean isSplitActive() {
+ return mMainStage.isActive();
+ }
+
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -366,11 +375,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
targetStage = mSideStage;
sideStagePosition = stagePosition;
} else {
- if (mMainStage.isActive()) {
+ if (isSplitScreenVisible()) {
// If the split screen is activated, retrieves target stage based on position.
targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
sideStagePosition = mSideStagePosition;
} else {
+ // Exit split if it running background.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+
targetStage = mSideStage;
sideStagePosition = stagePosition;
}
@@ -384,15 +396,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ null, this, null /* consumedCallback */, (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
} else {
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
@@ -423,41 +431,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
- prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionConsumed(boolean aborted) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted
- && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- }
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int transitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ mSplitTransitions.startEnterTransition(transitType, wct, null, this,
+ null /* consumedCallback */,
+ (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
}
/** Launches an activity into split by legacy transition. */
- void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictChildTasks(position, evictWct);
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final boolean isEnteringSplit = !isSplitActive();
LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
@Override
@@ -465,27 +468,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
}
}
+ }
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
+ if (isEnteringSplit && !openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
}
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
}
}
t.apply();
@@ -498,7 +502,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- mSyncQueue.queue(evictWct);
+
+ if (!isEnteringSplit && openingToSide) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
}
};
@@ -507,7 +516,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If split still not active, apply windows bounds first to avoid surface reset to
// wrong pos by SurfaceAnimator from wms.
- if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+ if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
@@ -516,14 +525,55 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
/** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
+
+ startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ }
+ /** Start an intent and a task to a split pair in one transition. */
+ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Starts a shortcut and a task to a split pair in one transition. */
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /**
+ * Starts with the second task to a split pair in one transition.
+ *
+ * @param wct transaction to start the first task
+ * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ */
+ 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);
@@ -538,77 +588,119 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.setForceTranslucent(mRootTaskInfo.token, false);
// Make sure the launch options will put tasks in the corresponding split roots
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
setEnterInstanceId(instanceId);
}
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ /** Starts a pair of tasks using legacy transition. */
+ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startTask(sideTaskId, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
- startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
- /** Start an intent and a task ordered by {@code intentFirst}. */
+ /** Starts a pair of intents using legacy transition. */
+ void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId);
+ }
+
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
+ /** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ private void startWithLegacyTransition(WindowContainerTransaction wct,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+ mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
+ null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
instanceId);
}
/**
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ if (!isSplitScreenVisible()) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
+
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
mSplitLayout.setDivideRatio(splitRatio);
+ // Apply surface bounds before animation start.
+ SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
+ startT.apply();
+ mTransactionPool.release(startT);
+
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
- mIsDividerRemoteAnimating = true;
+ mIsSplitEntering = true;
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
- prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
+ if (isSplitScreenVisible()) {
+ mMainStage.evictAllChildren(evictWct);
+ mSideStage.evictAllChildren(evictWct);
+ }
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
@@ -664,14 +756,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
updateWindowBounds(mSplitLayout, wct);
- wct.startTask(mainTaskId, mainOptions);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
});
setEnterInstanceId(instanceId);
@@ -685,7 +780,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
WindowContainerTransaction evictWct) {
- mIsDividerRemoteAnimating = false;
+ mIsSplitEntering = false;
mShouldUpdateRecents = true;
// If any stage has no child after animation finished, it means that split will display
// nothing, such status will happen if task and intent is same app but not support
@@ -694,8 +789,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
}
}
@@ -725,13 +824,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
- if (mMainStage.isActive()) {
+ if (isSplitScreenVisible()) {
// Use the stage of the specified position
options = resolveStartStage(
position == mSideStagePosition ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN,
@@ -798,19 +896,52 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
+ topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
+ bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
- () -> {
- setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition),
- null /* wct */);
- mTransactionPool.release(t);
+ insets -> {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(st -> {
+ updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
+ st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
+ st.setPosition(bottomRightScreenshot, insets.left, insets.top);
+
+ final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+ va.addUpdateListener(valueAnimator-> {
+ final float progress = (float) valueAnimator.getAnimatedValue();
+ t.setAlpha(topLeftScreenshot, progress);
+ t.setAlpha(bottomRightScreenshot, progress);
+ t.apply();
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(
+ @androidx.annotation.NonNull Animator animation) {
+ t.remove(topLeftScreenshot);
+ t.remove(bottomRightScreenshot);
+ t.apply();
+ mTransactionPool.release(t);
+ }
+ });
+ va.start();
+ });
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -841,20 +972,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- if (ENABLE_SHELL_TRANSITIONS) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(wct, this,
- mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
- } else {
- exitSplitScreen(
- mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
- }
- return;
- }
-
setDividerVisibility(!mKeyguardShowing, null);
}
@@ -928,14 +1045,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
mShouldUpdateRecents = false;
- mIsDividerRemoteAnimating = false;
+ mIsSplitEntering = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
- if (childrenToTop == null) {
+ if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -947,6 +1063,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
}
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* reparentLeafTaskIfRelaunch */);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setWindowCrop(mMainStage.mRootLeash, null)
@@ -965,7 +1083,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1001,15 +1118,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
- if (ENABLE_SHELL_TRANSITIONS) {
- StageTaskListener stageToTop = mSideStagePosition == stageToClose
- ? mMainStage
- : mSideStage;
- exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
- } else {
- boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
}
/**
@@ -1022,7 +1132,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
} catch (RemoteException | NullPointerException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Unable to update focus on the chosen stage, %s", TAG, e);
+ "Unable to update focus on the chosen stage: %s", e.getMessage());
}
}
@@ -1033,16 +1143,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
switch (exitReason) {
// One of the apps doesn't support MW
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
- // User has explicitly dragged the divider to dismiss split
+ // User has explicitly dragged the divider to dismiss split
case EXIT_REASON_DRAG_DIVIDER:
- // Either of the split apps have finished
+ // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED:
- // One of the children enters PiP
+ // One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
- // One of the apps occludes lock screen.
+ // One of the apps occludes lock screen.
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
- // User has unlocked the device after folded
+ // User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
+ // The device is folded
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ // User has used a keyboard shortcut to go back to fullscreen from split
return true;
default:
return false;
@@ -1190,13 +1303,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- private void onStageChildTaskEnterPip() {
- // When the exit split-screen is caused by one of the task enters auto pip,
- // we want both tasks to be put to bottom instead of top, otherwise it will end up
- // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
- exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP);
- }
-
private void updateRecentTasksSplitPair() {
if (!mShouldUpdateRecents) {
return;
@@ -1268,7 +1374,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
- mIsDividerRemoteAnimating = false;
+ mIsSplitEntering = false;
mSplitLayout.update(null /* t */);
onLayoutSizeChanged(mSplitLayout);
}
@@ -1317,6 +1423,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
}
+ void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
+ && !mIsSplitEntering) {
+ // Handle entring split case here if split already running background.
+ if (mIsDropEntering) {
+ mSplitLayout.resetDividerPosition();
+ } else {
+ mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mMainStage.reparentTopTask(wct);
+ mMainStage.evictAllChildren(wct);
+ mSideStage.evictOtherChildren(wct, taskId);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
+
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ if (mIsDropEntering) {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ mIsDropEntering = false;
+ } else {
+ mShowDecorImmediately = true;
+ mSplitLayout.flingDividerToCenter();
+ }
+ });
+ }
+ }
+
private void onRootTaskVanished() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mRootTaskInfo != null) {
@@ -1335,20 +1471,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
if (!mainStageVisible) {
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ true /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
// Both stages are not visible, check if it needs to dismiss split screen.
- if (mExitSplitScreenOnHide
- // Don't dismiss split screen when both stages are not visible due to sleeping
- // display.
- || (!mMainStage.mRootTaskInfo.isSleeping
- && !mSideStage.mRootTaskInfo.isSleeping)) {
+ if (mExitSplitScreenOnHide) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
}
+ } else {
+ wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ false /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
}
-
+ mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
- t.setVisibility(mSideStage.mRootLeash, sideStageVisible)
- .setVisibility(mMainStage.mRootLeash, mainStageVisible);
setDividerVisibility(mainStageVisible, t);
});
}
@@ -1359,23 +1497,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Request to %s divider bar from %s.", TAG,
+ "Request to %s divider bar from %s.",
(visible ? "show" : "hide"), Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
if (visible && mKeyguardShowing) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Defer showing divider bar due to keyguard showing.", TAG);
+ " Defer showing divider bar due to keyguard showing.");
return;
}
mDividerVisible = visible;
sendSplitVisibilityChanged();
- if (mIsDividerRemoteAnimating) {
+ if (mIsSplitEntering) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1390,12 +1528,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to divider leash not ready.", TAG);
+ " Skip animating divider bar due to divider leash not ready.");
return;
}
- if (mIsDividerRemoteAnimating) {
+ if (mIsSplitEntering) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1449,26 +1587,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
if (isSideStage && mMainStageListener.mVisible) {
// Exit to main stage if side stage no longer has children.
- if (ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED);
- } else {
- mSplitLayout.flingDividerToDismiss(
- mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
- EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(
+ mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
} else if (!isSideStage && mSideStageListener.mVisible) {
// Exit to side stage if main stage no longer has children.
- if (ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
- } else {
- mSplitLayout.flingDividerToDismiss(
- mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
- EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(
+ mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
+ } else if (!isSplitScreenVisible() && !mIsSplitEntering) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mLogger.isEnterRequestedByDrag()) {
prepareEnterSplitScreen(wct);
} else {
@@ -1483,9 +1616,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
- if (mLogger.isEnterRequestedByDrag()) {
+ if (mIsDropEntering) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ mIsDropEntering = false;
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1512,15 +1647,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
- ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mFocusingTaskInfo;
- }
-
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mFocusingTaskInfo = taskInfo;
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
@@ -1542,10 +1668,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
@@ -1558,29 +1681,45 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onLayoutSizeChanging(SplitLayout layout) {
+ public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t);
- mSideStage.onResizing(mTempRect2, mTempRect1, t);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ // Reset this flag every time onLayoutSizeChanged.
+ mShowDecorImmediately = false;
+
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ // Only need screenshot for legacy case because shell transition should screenshot
+ // itself during transition.
+ final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ mMainStage.screenshotIfNeeded(startT);
+ mSideStage.screenshotIfNeeded(startT);
+ mTransactionPool.release(startT);
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
- mMainStage.onResized(t);
- mSideStage.onResized(t);
- });
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+ } else {
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+ mMainStage.onResized(t);
+ mSideStage.onResized(t);
+ });
+ }
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
@@ -1671,14 +1810,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sendOnBoundsChanged();
}
- private void onFoldedStateChanged(boolean folded) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ @VisibleForTesting
+ void onFoldedStateChanged(boolean folded) {
+ int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
+ if (!mMainStage.isActive()) return;
+
if (mMainStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
} else if (mSideStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+
+ if (ENABLE_SHELL_TRANSITIONS) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(wct, this,
+ topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
}
}
@@ -1727,6 +1880,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@StageType
private int getStageType(StageTaskListener stage) {
+ if (stage == null) return STAGE_TYPE_UNDEFINED;
return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
}
@@ -1789,7 +1943,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionCallback);
+ mRecentTransitionFinishedCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
@@ -1803,8 +1957,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.setEnterTransition(
- transition, request.getRemoteTransition(), null /* callback */);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ null /* consumedCallback */, null /* finishedCallback */);
}
}
return out;
@@ -1823,7 +1977,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
final @WindowManager.TransitionType int type = request.getType();
if (isSplitActive() && !isOpeningType(type)
- && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ "empty during a mixed transition (one not handled by split),"
+ " so make sure split-screen state is cleaned-up. "
@@ -1833,10 +1987,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- public boolean isSplitActive() {
- return mMainStage.isActive();
- }
-
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
@@ -1933,6 +2083,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+ } else if (mSplitTransitions.isPendingResize(transition)) {
+ mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+ finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+ mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+ mSideStage.getSplitDecorManager());
+ return true;
}
if (!shouldAnimate) return false;
@@ -1958,8 +2114,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Update divider state after animation so that it is still around and positioned
// properly for the animation itself.
mSplitLayout.release();
- mSplitLayout.resetDividerPosition();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
@@ -1981,17 +2135,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- // TODO: fallback logic. Probably start a new transition to exit split before applying
- // anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
- throw new IllegalStateException("Launched a task in split, but didn't receive any"
- + " task in transition.");
+ Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+ return true;
}
} else {
if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
+ final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
+ (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
+ mSplitTransitions.mPendingEnter.cancel(
+ (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ return true;
}
}
@@ -2017,6 +2175,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
+ public void goToFullscreenFromSplit() {
+ boolean leftOrTop;
+ if (mSideStage.isFocused()) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@@ -2173,11 +2341,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* Sets drag info to be logged when splitscreen is next entered.
*/
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ if (!isSplitScreenVisible()) {
+ mIsDropEntering = true;
+ }
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
mLogger.enterRequestedByDrag(position, dragSessionId);
}
/**
+ * Sets info to be logged when splitscreen is next entered.
+ */
+ public void onRequestToSplit(InstanceId sessionId, int enterReason) {
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
+ mLogger.enterRequested(sessionId, enterReason);
+ }
+
+ /**
* Logs the exit of splitscreen.
*/
private void logExit(@ExitReason int exitReason) {
@@ -2212,6 +2398,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
+ public void onChildTaskAppeared(int taskId) {
+ StageCoordinator.this.onChildTaskAppeared(this, taskId);
+ }
+
+ @Override
public void onStatusChanged(boolean visible, boolean hasChildren) {
if (!mHasRootTask) return;
@@ -2231,11 +2422,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onChildTaskEnterPip() {
- StageCoordinator.this.onStageChildTaskEnterPip();
- }
-
- @Override
public void onRootTaskVanished() {
reset();
StageCoordinator.this.onRootTaskVanished();
@@ -2244,22 +2430,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
- final Toast splitUnsupportedToast = Toast.makeText(mContext,
- R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- splitUnsupportedToast.show();
+ mSplitUnsupportedToast.show();
return;
}
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- splitUnsupportedToast.show();
+ mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6b90eabe3bd2..a841b7f96d3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -18,11 +18,11 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -70,12 +70,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
public interface StageListenerCallbacks {
void onRootTaskAppeared();
+ void onChildTaskAppeared(int taskId);
+
void onStatusChanged(boolean visible, boolean hasChildren);
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
- void onChildTaskEnterPip();
-
void onRootTaskVanished();
void onNoLongerSupportMultiWindow();
@@ -188,6 +188,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
// Status is managed/synchronized by the transition lifecycle.
return;
}
+ mCallbacks.onChildTaskAppeared(taskId);
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -257,9 +258,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
// Status is managed/synchronized by the transition lifecycle.
return;
}
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- mCallbacks.onChildTaskEnterPip();
- }
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -288,15 +286,23 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
- void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t) {
+ void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
- mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t);
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
+ offsetY, immediately);
}
}
void onResized(SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
- mSplitDecorManager.onResized(t);
+ mSplitDecorManager.onResized(t, null);
+ }
+ }
+
+ void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (mSplitDecorManager != null) {
+ mSplitDecorManager.screenshotIfNeeded(t);
}
}
@@ -308,6 +314,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ SplitDecorManager getSplitDecorManager() {
+ return mSplitDecorManager;
+ }
+
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
@@ -332,6 +342,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void evictOtherChildren(WindowContainerTransaction wct, int taskId) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ if (taskId == taskInfo.taskId) continue;
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+
void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
for (int i = 0; i < apps.length; i++) {
@@ -354,6 +372,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void reparentTopTask(WindowContainerTransaction wct) {
+ wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token,
+ CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */, true /* reparentTopOnly */);
+ }
+
void resetBounds(WindowContainerTransaction wct) {
wct.setBounds(mRootTaskInfo.token, null);
wct.setAppBounds(mRootTaskInfo.token, null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 014f02bcf8b7..20da8773f387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -15,38 +15,20 @@
*/
package com.android.wm.shell.startingsurface;
-import static android.view.Choreographer.CALLBACK_COMMIT;
import static android.view.View.GONE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RadialGradient;
import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.MathUtils;
import android.util.Slog;
-import android.view.Choreographer;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import android.window.SplashScreenView;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.TransactionPool;
/**
@@ -55,14 +37,8 @@ import com.android.wm.shell.common.TransactionPool;
*/
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private static final boolean DEBUG_EXIT_ANIMATION = false;
- private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
private static final String TAG = StartingWindowController.TAG;
- private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
- private static final Interpolator MASK_RADIUS_INTERPOLATOR =
- new PathInterpolator(0f, 0f, 0.4f, 1f);
- private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
-
private final SurfaceControl mFirstWindowSurface;
private final Rect mFirstWindowFrame = new Rect();
private final SplashScreenView mSplashScreenView;
@@ -74,16 +50,17 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
+ // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+ private final float mRoundedCornerRadius;
- private ValueAnimator mMainAnimator;
- private ShiftUpAnimation mShiftUpAnimation;
- private RadialVanishAnimation mRadialVanishAnimation;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
- Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+ Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+ float roundedCornerRadius) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
+ mRoundedCornerRadius = roundedCornerRadius;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
@@ -121,187 +98,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
}
void startAnimations() {
- mMainAnimator = createAnimator();
- mMainAnimator.start();
- }
-
- // fade out icon, reveal app, shift up main window
- private ValueAnimator createAnimator() {
- // reveal app
- final float transparentRatio = 0.8f;
- final int globalHeight = mSplashScreenView.getHeight();
- final int verticalCircleCenter = 0;
- final int finalVerticalLength = globalHeight - verticalCircleCenter;
- final int halfWidth = mSplashScreenView.getWidth() / 2;
- final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
- Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
- final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
- final float[] stops = {0f, transparentRatio, 1f};
-
- mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
- mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
- mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
- mRadialVanishAnimation.setRadialPaintParam(colors, stops);
-
- if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
- // shift up main window
- View occludeHoleView = new View(mSplashScreenView.getContext());
- if (DEBUG_EXIT_ANIMATION_BLEND) {
- occludeHoleView.setBackgroundColor(Color.BLUE);
- } else {
- occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
- }
- final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
- mSplashScreenView.addView(occludeHoleView, params);
-
- mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
- }
-
- ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
- animator.setDuration(mAnimationDuration);
- animator.setInterpolator(Interpolators.LINEAR);
- animator.addListener(this);
- animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
- return animator;
- }
-
- private static class RadialVanishAnimation extends View {
- private final SplashScreenView mView;
- private int mInitRadius;
- private int mFinishRadius;
-
- private final Point mCircleCenter = new Point();
- private final Matrix mVanishMatrix = new Matrix();
- private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- RadialVanishAnimation(SplashScreenView target) {
- super(target.getContext());
- mView = target;
- mView.addView(this);
- mVanishPaint.setAlpha(0);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mVanishPaint.getShader() == null) {
- return;
- }
-
- final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
- final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
- final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
-
- mVanishMatrix.setScale(scale, scale);
- mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
- mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
- mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
-
- postInvalidate();
- }
-
- void setRadius(int initRadius, int finishRadius) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
- + " final " + finishRadius);
- }
- mInitRadius = initRadius;
- mFinishRadius = finishRadius;
- }
-
- void setCircleCenter(int x, int y) {
- if (DEBUG_EXIT_ANIMATION) {
- Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
- }
- mCircleCenter.set(x, y);
- }
-
- void setRadialPaintParam(int[] colors, float[] stops) {
- // setup gradient shader
- final RadialGradient rShader =
- new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
- mVanishPaint.setShader(rShader);
- if (!DEBUG_EXIT_ANIMATION_BLEND) {
- // We blend the reveal gradient with the splash screen using DST_OUT so that the
- // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
- // fully invisible when radius = finishRadius AND gradient opacity is 1.
- mVanishPaint.setBlendMode(BlendMode.DST_OUT);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
- }
- }
-
- private final class ShiftUpAnimation {
- private final float mFromYDelta;
- private final float mToYDelta;
- private final View mOccludeHoleView;
- private final SyncRtSurfaceTransactionApplier mApplier;
- private final Matrix mTmpTransform = new Matrix();
-
- ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
- mFromYDelta = fromYDelta;
- mToYDelta = toYDelta;
- mOccludeHoleView = occludeHoleView;
- mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
- }
-
- void onAnimationProgress(float linearProgress) {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
- || !mSplashScreenView.isAttachedToWindow()) {
- return;
- }
-
- final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
- final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
-
- mOccludeHoleView.setTranslationY(dy);
- mTmpTransform.setTranslate(0 /* dx */, dy);
-
- // set the vsyncId to ensure the transaction doesn't get applied too early.
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- mTmpTransform.postTranslate(mFirstWindowFrame.left,
- mFirstWindowFrame.top + mMainWindowShiftLength);
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withMatrix(mTmpTransform)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
-
- mTransactionPool.release(tx);
- }
-
- void finish() {
- if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
- return;
- }
- final SurfaceControl.Transaction tx = mTransactionPool.acquire();
- if (mSplashScreenView.isAttachedToWindow()) {
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams
- .Builder(mFirstWindowSurface)
- .withWindowCrop(null)
- .withMergeTransaction(tx)
- .build();
- mApplier.scheduleApply(params);
- } else {
- tx.setWindowCrop(mFirstWindowSurface, null);
- tx.apply();
- }
- mTransactionPool.release(tx);
-
- Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
- mFirstWindowSurface::release, null);
- }
+ SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+ mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+ mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+ mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
@@ -316,9 +116,6 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
mFinishCallback = null;
}
}
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.finish();
- }
}
@Override
@@ -342,40 +139,4 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
public void onAnimationRepeat(Animator animation) {
// ignore
}
-
- private void onFadeOutProgress(float linearProgress) {
- final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
- getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
- final View iconView = mSplashScreenView.getIconView();
- final View brandingView = mSplashScreenView.getBrandingView();
- if (iconView != null) {
- iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
- }
- if (brandingView != null) {
- brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
- }
- }
-
- private void onAnimationProgress(float linearProgress) {
- onFadeOutProgress(linearProgress);
-
- final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
- mAppRevealDuration);
-
- if (mRadialVanishAnimation != null) {
- mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
- }
-
- if (mShiftUpAnimation != null) {
- mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
- }
- }
-
- private float getProgress(float linearProgress, long delay, long duration) {
- return MathUtils.constrain(
- (linearProgress * (mAnimationDuration) - delay) / duration,
- 0.0f,
- 1.0f
- );
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
new file mode 100644
index 000000000000..a7e4385b60c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -0,0 +1,375 @@
+/*
+ * 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.startingsurface;
+
+import static android.view.Choreographer.CALLBACK_COMMIT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.SplashScreenView;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.TransactionPool;
+
+/**
+ * Utilities for creating the splash screen window animations.
+ * @hide
+ */
+public class SplashScreenExitAnimationUtils {
+ private static final boolean DEBUG_EXIT_ANIMATION = false;
+ private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final String TAG = "SplashScreenExitAnimationUtils";
+
+ private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
+ private static final Interpolator MASK_RADIUS_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0.4f, 1f);
+ private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window with rounded corner radius.
+ */
+ static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
+ ValueAnimator animator =
+ createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, roundedCornerRadius);
+ animator.start();
+ }
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window.
+ * @hide
+ */
+ public static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, 0f /* roundedCornerRadius */);
+ }
+
+ /**
+ * Creates the animator to fade out the icon, reveal the app, and shift up main window.
+ * @hide
+ */
+ private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
+ // reveal app
+ final float transparentRatio = 0.8f;
+ final int globalHeight = splashScreenView.getHeight();
+ final int verticalCircleCenter = 0;
+ final int finalVerticalLength = globalHeight - verticalCircleCenter;
+ final int halfWidth = splashScreenView.getWidth() / 2;
+ final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
+ Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
+ final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
+ final float[] stops = {0f, transparentRatio, 1f};
+
+ RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
+ radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
+ radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
+ radialVanishAnimation.setRadialPaintParam(colors, stops);
+
+ View occludeHoleView = null;
+ ShiftUpAnimation shiftUpAnimation = null;
+ if (firstWindowSurface != null && firstWindowSurface.isValid()) {
+ // shift up main window
+ occludeHoleView = new View(splashScreenView.getContext());
+ if (DEBUG_EXIT_ANIMATION_BLEND) {
+ occludeHoleView.setBackgroundColor(Color.BLUE);
+ } else if (splashScreenView instanceof SplashScreenView) {
+ occludeHoleView.setBackgroundColor(
+ ((SplashScreenView) splashScreenView).getInitBackgroundColor());
+ } else {
+ occludeHoleView.setBackgroundColor(
+ isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
+ }
+ final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
+ splashScreenView.addView(occludeHoleView, params);
+
+ shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
+ firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
+ mMainWindowShiftLength, roundedCornerRadius);
+ }
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(animationDuration);
+ animator.setInterpolator(Interpolators.LINEAR);
+ if (animatorListener != null) {
+ animator.addListener(animatorListener);
+ }
+ View finalOccludeHoleView = occludeHoleView;
+ ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.finish();
+ }
+ splashScreenView.removeView(radialVanishAnimation);
+ splashScreenView.removeView(finalOccludeHoleView);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Fade out progress
+ final float iconProgress =
+ ICON_INTERPOLATOR.getInterpolation(getProgress(
+ linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+ View iconView = null;
+ View brandingView = null;
+ if (splashScreenView instanceof SplashScreenView) {
+ iconView = ((SplashScreenView) splashScreenView).getIconView();
+ brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+ }
+ if (iconView != null) {
+ iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
+ }
+
+ final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration);
+
+ radialVanishAnimation.onAnimationProgress(revealLinearProgress);
+
+ if (finalShiftUpAnimation != null) {
+ finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
+ }
+ });
+ return animator;
+ }
+
+ private static float getProgress(float linearProgress, long delay, long duration,
+ int animationDuration) {
+ return MathUtils.constrain(
+ (linearProgress * (animationDuration) - delay) / duration,
+ 0.0f,
+ 1.0f
+ );
+ }
+
+ private static boolean isDarkTheme(Context context) {
+ Configuration configuration = context.getResources().getConfiguration();
+ int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return nightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ /**
+ * View which creates a circular reveal of the underlying view.
+ * @hide
+ */
+ @SuppressLint("ViewConstructor")
+ public static class RadialVanishAnimation extends View {
+ private final ViewGroup mView;
+ private int mInitRadius;
+ private int mFinishRadius;
+
+ private final Point mCircleCenter = new Point();
+ private final Matrix mVanishMatrix = new Matrix();
+ private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public RadialVanishAnimation(ViewGroup target) {
+ super(target.getContext());
+ mView = target;
+ mView.addView(this);
+ if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+ ((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
+ }
+ mVanishPaint.setAlpha(0);
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mVanishPaint.getShader() == null) {
+ return;
+ }
+
+ final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
+ final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
+ final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
+
+ mVanishMatrix.setScale(scale, scale);
+ mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
+ mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
+ mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
+
+ postInvalidate();
+ }
+
+ void setRadius(int initRadius, int finishRadius) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
+ + " final " + finishRadius);
+ }
+ mInitRadius = initRadius;
+ mFinishRadius = finishRadius;
+ }
+
+ void setCircleCenter(int x, int y) {
+ if (DEBUG_EXIT_ANIMATION) {
+ Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
+ }
+ mCircleCenter.set(x, y);
+ }
+
+ void setRadialPaintParam(int[] colors, float[] stops) {
+ // setup gradient shader
+ final RadialGradient rShader =
+ new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
+ mVanishPaint.setShader(rShader);
+ if (!DEBUG_EXIT_ANIMATION_BLEND) {
+ // We blend the reveal gradient with the splash screen using DST_OUT so that the
+ // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
+ // fully invisible when radius = finishRadius AND gradient opacity is 1.
+ mVanishPaint.setBlendMode(BlendMode.DST_OUT);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
+ }
+ }
+
+ /**
+ * Shifts up the main window.
+ * @hide
+ */
+ public static final class ShiftUpAnimation {
+ private final float mFromYDelta;
+ private final float mToYDelta;
+ private final View mOccludeHoleView;
+ private final SyncRtSurfaceTransactionApplier mApplier;
+ private final Matrix mTmpTransform = new Matrix();
+ private final SurfaceControl mFirstWindowSurface;
+ private final ViewGroup mSplashScreenView;
+ private final TransactionPool mTransactionPool;
+ private final Rect mFirstWindowFrame;
+ private final int mMainWindowShiftLength;
+
+ public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
+ SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
+ TransactionPool transactionPool, Rect firstWindowFrame,
+ int mainWindowShiftLength, float roundedCornerRadius) {
+ mFromYDelta = fromYDelta - roundedCornerRadius;
+ mToYDelta = toYDelta;
+ mOccludeHoleView = occludeHoleView;
+ mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
+ mFirstWindowSurface = firstWindowSurface;
+ mSplashScreenView = splashScreenView;
+ mTransactionPool = transactionPool;
+ mFirstWindowFrame = firstWindowFrame;
+ mMainWindowShiftLength = mainWindowShiftLength;
+ }
+
+ void onAnimationProgress(float linearProgress) {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
+ || !mSplashScreenView.isAttachedToWindow()) {
+ return;
+ }
+
+ final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
+ final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
+
+ mOccludeHoleView.setTranslationY(dy);
+ mTmpTransform.setTranslate(0 /* dx */, dy);
+
+ // set the vsyncId to ensure the transaction doesn't get applied too early.
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ mTmpTransform.postTranslate(mFirstWindowFrame.left,
+ mFirstWindowFrame.top + mMainWindowShiftLength);
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withMatrix(mTmpTransform)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+
+ mTransactionPool.release(tx);
+ }
+
+ void finish() {
+ if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
+ return;
+ }
+ final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+ if (mSplashScreenView.isAttachedToWindow()) {
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+
+ SyncRtSurfaceTransactionApplier.SurfaceParams
+ params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+ .Builder(mFirstWindowSurface)
+ .withWindowCrop(null)
+ .withMergeTransaction(tx)
+ .build();
+ mApplier.scheduleApply(params);
+ } else {
+ tx.setWindowCrop(mFirstWindowSurface, null);
+ tx.apply();
+ }
+ mTransactionPool.release(tx);
+
+ Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
+ mFirstWindowSurface::release, null);
+ }
+ }
+}
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 8cee4f1dc8fb..839d56a43222 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
@@ -432,7 +432,8 @@ public class SplashscreenContentDrawer {
final ShapeIconFactory factory = new ShapeIconFactory(
SplashscreenContentDrawer.this.mContext,
scaledIconDpi, mFinalIconSize);
- final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
+ final Bitmap bitmap = factory.createScaledBitmap(iconDrawable,
+ BaseIconFactory.MODE_DEFAULT);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
createIconDrawable(new BitmapDrawable(bitmap), true,
mHighResIconProvider.mLoadInDetail);
@@ -992,10 +993,11 @@ public class SplashscreenContentDrawer {
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback, long createTime) {
+ Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
final Runnable playAnimation = () -> {
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
- view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+ roundedCornerRadius);
animation.startAnimations();
};
if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a39189b..538bbec2aa2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@ import android.graphics.Color;
* Interface to engage starting window feature.
*/
public interface StartingSurface {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate starting windows.
- */
- default IStartingWindow createExternalInterface() {
- return null;
- }
-
/**
* Returns the background color for a starting window if existing.
*/
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 ff6f2b03c02c..22e804547d5c 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
@@ -646,7 +646,7 @@ public class StartingSurfaceDrawer {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime);
+ record.mCreateTime, removalInfo.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
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 379af21ac956..be2e79342d07 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
@@ -23,6 +23,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -38,15 +39,18 @@ import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import androidx.annotation.BinderThread;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
/**
@@ -76,6 +80,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
@@ -86,12 +91,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
public StartingWindowController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
@@ -107,8 +114,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IStartingWindowImpl(this);
+ }
+
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ this::createExternalInterface, this);
}
@Override
@@ -126,10 +139,16 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
*
* @param listener The callback when need a starting window.
*/
+ @VisibleForTesting
void setStartingWindowListener(TriConsumer<Integer, Integer, Integer> listener) {
mTaskLaunchingCallback = listener;
}
+ @VisibleForTesting
+ boolean hasStartingWindowListener() {
+ return mTaskLaunchingCallback != null;
+ }
+
/**
* Called when a task need a starting window.
*/
@@ -222,17 +241,6 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
* The interface for calls from outside the Shell, within the host process.
*/
private class StartingSurfaceImpl implements StartingSurface {
- private IStartingWindowImpl mIStartingWindow;
-
- @Override
- public IStartingWindowImpl createExternalInterface() {
- if (mIStartingWindow != null) {
- mIStartingWindow.invalidate();
- }
- mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
- return mIStartingWindow;
- }
-
@Override
public int getBackgroundColor(TaskInfo taskInfo) {
synchronized (mTaskBackgroundColors) {
@@ -256,7 +264,8 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private static class IStartingWindowImpl extends IStartingWindow.Stub
+ implements ExternalInterfaceBinder {
private StartingWindowController mController;
private SingleInstanceRemoteListener<StartingWindowController,
IStartingWindowListener> mListener;
@@ -276,8 +285,11 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
+ // Unregister the listener to ensure any registered binder death recipients are unlinked
+ mListener.unregister();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 57993948886b..3f944cb6d628 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@ import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@ public class ShellController {
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+ new ArrayMap<>();
+ // References to the existing interfaces, to be invalidated when they are recreated
+ private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
private Configuration mLastConfiguration;
@@ -67,6 +77,11 @@ public class ShellController {
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/**
@@ -124,6 +139,48 @@ public class ShellController {
mUserChangeListeners.remove(listener);
}
+ /**
+ * Adds an interface that can be called from a remote process. This method takes a supplier
+ * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+ * will request new binder instances for each instance of Launcher that it provides binders
+ * to.
+ *
+ * @param extra the key for the interface, {@see ShellSharedConstants}
+ * @param binderSupplier the supplier of the binder to pass to the external process
+ * @param callerInstance the instance of the caller, purely for logging
+ */
+ public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+ Object callerInstance) {
+ ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+ callerInstance.getClass().getSimpleName(), extra);
+ if (mExternalInterfaceSuppliers.containsKey(extra)) {
+ throw new IllegalArgumentException("Supplier with same key already exists: "
+ + extra);
+ }
+ mExternalInterfaceSuppliers.put(extra, binderSupplier);
+ }
+
+ /**
+ * Updates the given bundle with the set of external interfaces, invalidating the old set of
+ * binders.
+ */
+ @VisibleForTesting
+ public void createExternalInterfaces(Bundle output) {
+ // Invalidate the old binders
+ for (int i = 0; i < mExternalInterfaces.size(); i++) {
+ mExternalInterfaces.valueAt(i).invalidate();
+ }
+ mExternalInterfaces.clear();
+
+ // Create new binders for each key
+ for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+ final String key = mExternalInterfaceSuppliers.keyAt(i);
+ final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+ mExternalInterfaces.put(key, b);
+ output.putBinder(key, b.asBinder());
+ }
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +261,14 @@ public class ShellController {
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+ if (!mExternalInterfaces.isEmpty()) {
+ pw.println(innerPrefix + "mExternalInterfaces={");
+ for (String key : mExternalInterfaces.keySet()) {
+ pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+ }
+ pw.println(innerPrefix + "}");
+ }
}
/**
@@ -211,7 +276,6 @@ public class ShellController {
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
-
@Override
public void onInit() {
try {
@@ -222,28 +286,6 @@ public class ShellController {
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = mShellCommandHandler.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +316,38 @@ public class ShellController {
mMainExecutor.execute(() ->
ShellController.this.onUserProfilesChanged(profiles));
}
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void createExternalInterfaces(Bundle bundle) {
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ ShellController.this.createExternalInterfaces(bundle);
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to get Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c824ac6f..bc5dd11ef54e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.sysui;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -37,18 +38,6 @@ public interface ShellInterface {
default void onInit() {}
/**
- * Dumps the shell state.
- */
- default void dump(PrintWriter pw) {}
-
- /**
- * Handles a shell command.
- */
- default boolean handleCommand(final String[] args, PrintWriter pw) {
- return false;
- }
-
- /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@ public interface ShellInterface {
* Notifies the Shell when a profile belonging to the user changes.
*/
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Updates the given {@param bundle} with the set of exposed interfaces.
+ */
+ default void createExternalInterfaces(Bundle bundle) {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 000000000000..bdda6a8e926b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
+ // See IRecentTasks.aidl
+ public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+ // See IBackAnimation.aidl
+ public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IFloatingTasks.aidl
+ public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
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 3cba92956f95..a2d7bc43653a 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
@@ -111,7 +111,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
+ if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitScreenVisible()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
+ "Split-Screen is active, so treat it as Mixed.");
if (request.getRemoteTransition() != null) {
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 dbb2948de5db..928e71f8d3a6 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
@@ -46,6 +46,7 @@ import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -59,6 +60,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+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;
@@ -76,10 +78,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Canvas;
import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -89,7 +88,6 @@ import android.os.IBinder;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -322,6 +320,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
+ | FLAG_IS_BEHIND_STARTING_WINDOW)) {
+ // Don't animate embedded activity if it is covered by the starting window.
+ // Non-embedded case still needs animation because the container can still animate
+ // the starting window together, e.g. CLOSE or CHANGE type.
+ continue;
+ }
+ if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
+ // Wallpaper, IME, and system windows don't need any default animations.
+ continue;
+ }
final boolean isTask = change.getTaskInfo() != null;
boolean isSeamlessDisplayChange = false;
@@ -525,123 +534,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
- private void edgeExtendWindow(TransitionInfo.Change change,
- Animation a, SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction) {
- // Do not create edge extension surface for transfer starting window change.
- // The app surface could be empty thus nothing can draw on the hardware renderer, which will
- // block this thread when calling Surface#unlockCanvasAndPost.
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- return;
- }
- final Transformation transformationAtStart = new Transformation();
- a.getTransformationAt(0, transformationAtStart);
- final Transformation transformationAtEnd = new Transformation();
- a.getTransformationAt(1, transformationAtEnd);
-
- // We want to create an extension surface that is the maximal size and the animation will
- // take care of cropping any part that overflows.
- final Insets maxExtensionInsets = Insets.min(
- transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
- final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
- change.getEndAbsBounds().height());
- final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
- change.getEndAbsBounds().width());
- if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = maxExtensionInsets.left;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Left Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = 0;
- final int yPos = maxExtensionInsets.top;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Top Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = targetSurfaceWidth;
- final int yPos = 0;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Right Edge Extension", startTransaction, finishTransaction);
- }
-
- if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
- targetSurfaceWidth, targetSurfaceHeight);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = maxExtensionInsets.left;
- final int yPos = targetSurfaceHeight;
- createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
- "Bottom Edge Extension", startTransaction, finishTransaction);
- }
- }
-
- private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- SurfaceControl.Transaction startTransaction,
- SurfaceControl.Transaction finishTransaction) {
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setParent(surfaceToExtend)
- .setHidden(true)
- .setCallsite("DefaultTransitionHandler#startAnimation")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
- SurfaceControl.LayerCaptureArgs captureArgs =
- new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
- .setSourceCrop(edgeBounds)
- .setFrameScale(1)
- .setPixelFormat(PixelFormat.RGBA_8888)
- .setChildrenOnly(true)
- .setAllowProtected(true)
- .build();
- final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
- SurfaceControl.captureLayers(captureArgs);
-
- if (edgeBuffer == null) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Failed to capture edge of window.");
- return null;
- }
-
- android.graphics.BitmapShader shader =
- new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
- android.graphics.Shader.TileMode.CLAMP,
- android.graphics.Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
-
- final Surface surface = new Surface(edgeExtensionLayer);
- Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
- surface.release();
-
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
- finishTransaction.remove(edgeExtensionLayer);
-
- return edgeExtensionLayer;
- }
-
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -739,12 +631,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(animation -> {
+ final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
- });
+ };
+ va.addUpdateListener(updateListener);
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
@@ -757,20 +650,30 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
});
};
va.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
private boolean mFinished = false;
@Override
public void onAnimationEnd(Animator animation) {
- if (mFinished) return;
- mFinished = true;
- finisher.run();
+ onFinish();
}
@Override
public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
if (mFinished) return;
mFinished = true;
finisher.run();
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ va.removeUpdateListener(updateListener);
}
});
animations.add(va);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa290270d..485b400f458d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
}
@@ -90,7 +90,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+ startTransaction, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -124,7 +130,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
}
};
try {
- mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT =
+ RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().mergeAnimation(
+ transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error merging remote transition.", e);
}
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 9469529de8f1..b4e05848882c 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -120,10 +121,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
unhandleDeath(remote.asBinder(), finishCallback);
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
mRequestedRemotes.remove(transition);
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
@@ -131,8 +132,14 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
};
Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
- remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -145,6 +152,28 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+ IRemoteTransition remote) {
+ // We care more about parceling than local (though they should be the same); so, use
+ // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+ if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+ // No local interface, so binder itself will parcel and thus we don't need to.
+ return t;
+ }
+ // Binder won't be parceling; however, the remotes assume they have their own native
+ // objects (and don't know if caller is local or not), so we need to make a COPY here so
+ // that the remote can clean it up without clearing the original transaction.
+ // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+ final Parcel p = Parcel.obtain();
+ try {
+ t.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
};
try {
- remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d4ec42..da39017a0313 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@ import com.android.wm.shell.common.annotations.ExternalThread;
*/
@ExternalThread
public interface ShellTransitions {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate remote transitions.
- */
- default IShellTransitions createExternalInterface() {
- return null;
- }
-
/**
* Registers a remote 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 efee6f40b53e..ab792ee122c7 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
@@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
@@ -34,10 +35,19 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
+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.TransitionInfo;
import com.android.internal.R;
@@ -217,4 +227,127 @@ public class TransitionAnimationHelper {
.show(animationBackgroundSurface);
finishTransaction.remove(animationBackgroundSurface);
}
+
+ /**
+ * Adds edge extension surface to the given {@code change} for edge extension animation.
+ */
+ public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
+ @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ // Do not create edge extension surface for transfer starting window change.
+ // The app surface could be empty thus nothing can draw on the hardware renderer, which will
+ // block this thread when calling Surface#unlockCanvasAndPost.
+ if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+ return;
+ }
+ final Transformation transformationAtStart = new Transformation();
+ a.getTransformationAt(0, transformationAtStart);
+ final Transformation transformationAtEnd = new Transformation();
+ a.getTransformationAt(1, transformationAtEnd);
+
+ // We want to create an extension surface that is the maximal size and the animation will
+ // take care of cropping any part that overflows.
+ final Insets maxExtensionInsets = Insets.min(
+ transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+ final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+ change.getEndAbsBounds().height());
+ final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+ change.getEndAbsBounds().width());
+ if (maxExtensionInsets.left < 0) {
+ final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.left, targetSurfaceHeight);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Left Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.top < 0) {
+ final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.top);
+ final int xPos = 0;
+ final int yPos = maxExtensionInsets.top;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Top Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.right < 0) {
+ final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.right, targetSurfaceHeight);
+ final int xPos = targetSurfaceWidth;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Right Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.bottom < 0) {
+ final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.bottom);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = targetSurfaceHeight;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Bottom Edge Extension", startTransaction, finishTransaction);
+ }
+ }
+
+ /**
+ * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
+ * animation.
+ */
+ private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
+ @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
+ @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setParent(surfaceToExtend)
+ .setHidden(true)
+ .setCallsite("TransitionAnimationHelper#createExtensionSurface")
+ .setOpaque(true)
+ .setBufferSize(extensionRect.width(), extensionRect.height())
+ .build();
+
+ final SurfaceControl.LayerCaptureArgs captureArgs =
+ new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ .setSourceCrop(edgeBounds)
+ .setFrameScale(1)
+ .setPixelFormat(PixelFormat.RGBA_8888)
+ .setChildrenOnly(true)
+ .setAllowProtected(true)
+ .setCaptureSecureLayers(true)
+ .build();
+ final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+ SurfaceControl.captureLayers(captureArgs);
+
+ if (edgeBuffer == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Failed to capture edge of window.");
+ return null;
+ }
+
+ final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
+
+ final Surface surface = new Surface(edgeExtensionLayer);
+ final Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
+
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+ finishTransaction.remove(edgeExtensionLayer);
+
+ return edgeExtensionLayer;
+ }
}
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 29d25bc39223..39fe4559c88f 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
@@ -24,11 +24,12 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
-import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
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.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,11 +62,13 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
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 java.util.ArrayList;
@@ -115,8 +118,11 @@ public class Transitions implements RemoteCallable<Transitions> {
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private boolean mIsRegistered = false;
+
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -142,6 +148,7 @@ public class Transitions implements RemoteCallable<Transitions> {
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@@ -156,16 +163,19 @@ public class Transitions implements RemoteCallable<Transitions> {
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
- shellInit.addInitCallback(this::onInit, this);
- }
-
- private void onInit() {
+ mShellController = shellController;
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
@@ -176,13 +186,23 @@ public class Transitions implements RemoteCallable<Transitions> {
new SettingsObserver());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mIsRegistered = true;
// Register this transition handler with Core
- mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ try {
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ } catch (RuntimeException e) {
+ mIsRegistered = false;
+ throw e;
+ }
// Pre-load the instance.
TransitionMetrics.getInstance();
}
}
+ public boolean isRegistered() {
+ return mIsRegistered;
+ }
+
private float getTransitionAnimationScaleSetting() {
return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
@@ -193,6 +213,10 @@ public class Transitions implements RemoteCallable<Transitions> {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IShellTransitionsImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -308,9 +332,26 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
+ // Currently system windows are controlled by WindowState, so don't change their
+ // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
+ // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
+ // app), because there may not be a transition associated with their visibility
+ // changes, and currently they don't need transition animation.
+ continue;
+ }
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
+ if (mode == TRANSIT_TO_FRONT
+ && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
+ || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
+ // When the window is moved to front with a different size, make sure the crop is
+ // updated to prevent it from using the old crop.
+ t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+ }
+
// Don't move anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
@@ -329,20 +370,9 @@ public class Transitions implements RemoteCallable<Transitions> {
// If this is a transferred starting window, we want it immediately visible.
&& (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
t.setAlpha(leash, 0.f);
- // fix alpha in finish transaction in case the animator itself no-ops.
- finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
- // As a result, we actually can't hide their WindowTokens because there may not be a
- // transition associated with them becoming visible again. Fortunately, since
- // wallpapers are always z-ordered to the back, we don't have to worry about it
- // flickering to the front during reparenting. Similarly, the IME is reparented to
- // the associated app, so its visibility is coupled. So, an explicit hide is not
- // needed visually anyways.
- if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
- finishT.hide(leash);
- }
+ finishT.hide(leash);
}
}
}
@@ -441,32 +471,36 @@ public class Transitions implements RemoteCallable<Transitions> {
return;
}
- // apply transfer starting window directly if there is no other task change. Since this
- // is an activity->activity situation, we can detect it by selecting transitions with only
- // 2 changes where neither are tasks and one is a starting-window recipient.
final int changeSize = info.getChanges().size();
- if (changeSize == 2) {
- boolean nonTaskChange = true;
- boolean transferStartingWindow = false;
- for (int i = changeSize - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null) {
- nonTaskChange = false;
- break;
- }
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- transferStartingWindow = true;
- }
- }
- if (nonTaskChange && transferStartingWindow) {
- t.apply();
- finishT.apply();
- // Treat this as an abort since we are bypassing any merge logic and effectively
- // finishing immediately.
- onAbort(transitionToken);
- return;
+ boolean taskChange = false;
+ boolean transferStartingWindow = false;
+ boolean allOccluded = changeSize > 0;
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ taskChange |= change.getTaskInfo() != null;
+ transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+ if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
+ allOccluded = false;
}
}
+ // There does not need animation when:
+ // A. Transfer starting window. Apply transfer starting window directly if there is no other
+ // task change. Since this is an activity->activity situation, we can detect it by selecting
+ // transitions with only 2 changes where neither are tasks and one is a starting-window
+ // recipient.
+ if (!taskChange && transferStartingWindow && changeSize == 2
+ // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
+ // changes are underneath another change.
+ || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
+ && 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);
+ return;
+ }
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mInfo = info;
@@ -542,6 +576,22 @@ public class Transitions implements RemoteCallable<Transitions> {
"This shouldn't happen, maybe the default handler is broken.");
}
+ /**
+ * 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) {
+ 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 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 */);
@@ -553,6 +603,15 @@ public class Transitions implements RemoteCallable<Transitions> {
onFinish(transition, wct, wctCB, false /* abort */);
}
+ /**
+ * Releases an info's animation-surfaces. These don't need to persist and we need to release
+ * them asap so that SF can free memory sooner.
+ */
+ private void releaseSurfaces(@Nullable TransitionInfo info) {
+ if (info == null) return;
+ info.releaseAnimSurfaces();
+ }
+
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB,
@@ -591,6 +650,11 @@ public class Transitions implements RemoteCallable<Transitions> {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ 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();
+ }
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -610,12 +674,14 @@ public class Transitions implements RemoteCallable<Transitions> {
fullFinish.apply();
}
// Now perform all the finishes.
+ 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
@@ -628,8 +694,9 @@ public class Transitions implements RemoteCallable<Transitions> {
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(active.mToken, true);
+ mObservers.get(i).onTransitionFinished(aborted.mToken, true);
}
+ releaseSurfaces(aborted.mInfo);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -716,8 +783,8 @@ public class Transitions implements RemoteCallable<Transitions> {
null /* newDisplayAreaInfo */);
}
}
- active.mToken = mOrganizer.startTransition(
- request.getType(), transitionToken, wct);
+ mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
+ active.mToken = transitionToken;
mActiveTransitions.add(active);
}
@@ -726,7 +793,7 @@ public class Transitions implements RemoteCallable<Transitions> {
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
- active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
+ active.mToken = mOrganizer.startNewTransition(type, wct);
mActiveTransitions.add(active);
return active.mToken;
}
@@ -897,17 +964,6 @@ public class Transitions implements RemoteCallable<Transitions> {
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
- private IShellTransitionsImpl mIShellTransitions;
-
- @Override
- public IShellTransitions createExternalInterface() {
- if (mIShellTransitions != null) {
- mIShellTransitions.invalidate();
- }
- mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
- return mIShellTransitions;
- }
-
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull RemoteTransition remoteTransition) {
@@ -928,7 +984,8 @@ public class Transitions implements RemoteCallable<Transitions> {
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub
+ implements ExternalInterfaceBinder {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
@@ -938,7 +995,8 @@ public class Transitions implements RemoteCallable<Transitions> {
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mTransitions = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 6b59e313b01b..d7cb490ed0cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.unfold;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -56,6 +54,12 @@ public class UnfoldAnimationController implements UnfoldListener {
private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
+ /**
+ * Indicates whether we're in stage change process. This should be set to {@code true} in
+ * {@link #onStateChangeStarted()} and {@code false} in {@link #onStateChangeFinished()}.
+ */
+ private boolean mIsInStageChange;
+
public UnfoldAnimationController(
@NonNull ShellInit shellInit,
@NonNull TransactionPool transactionPool,
@@ -123,7 +127,7 @@ public class UnfoldAnimationController implements UnfoldListener {
animator.onTaskChanged(taskInfo);
} else {
// Became inapplicable
- resetTask(animator, taskInfo);
+ maybeResetTask(animator, taskInfo);
animator.onTaskVanished(taskInfo);
mAnimatorsByTaskId.remove(taskInfo.taskId);
}
@@ -154,7 +158,7 @@ public class UnfoldAnimationController implements UnfoldListener {
final boolean isCurrentlyApplicable = animator != null;
if (isCurrentlyApplicable) {
- resetTask(animator, taskInfo);
+ maybeResetTask(animator, taskInfo);
animator.onTaskVanished(taskInfo);
mAnimatorsByTaskId.remove(taskInfo.taskId);
}
@@ -166,6 +170,7 @@ public class UnfoldAnimationController implements UnfoldListener {
return;
}
+ mIsInStageChange = true;
SurfaceControl.Transaction transaction = null;
for (int i = 0; i < mAnimators.size(); i++) {
final UnfoldTaskAnimator animator = mAnimators.get(i);
@@ -219,11 +224,12 @@ public class UnfoldAnimationController implements UnfoldListener {
transaction.apply();
mTransactionPool.release(transaction);
+ mIsInStageChange = false;
}
- private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
+ private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
+ if (!mIsInStageChange) {
+ // No need to resetTask if there is no ongoing state change.
return;
}
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index e90389764af3..f209521b1da4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -33,6 +33,8 @@ public class SplitBounds implements Parcelable {
// This class is orientation-agnostic, so we compute both for later use
public final float topTaskPercent;
public final float leftTaskPercent;
+ public final float dividerWidthPercent;
+ public final float dividerHeightPercent;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -62,8 +64,12 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = false;
}
- leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
- topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+ float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+ float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+ leftTaskPercent = leftTopBounds.width() / totalWidth;
+ topTaskPercent = leftTopBounds.height() / totalHeight;
+ dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+ dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
public SplitBounds(Parcel parcel) {
@@ -75,6 +81,8 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = parcel.readBoolean();
leftTopTaskId = parcel.readInt();
rightBottomTaskId = parcel.readInt();
+ dividerWidthPercent = parcel.readInt();
+ dividerHeightPercent = parcel.readInt();
}
@Override
@@ -87,6 +95,8 @@ public class SplitBounds implements Parcelable {
parcel.writeBoolean(appsStackedVertically);
parcel.writeInt(leftTopTaskId);
parcel.writeInt(rightBottomTaskId);
+ parcel.writeFloat(dividerWidthPercent);
+ parcel.writeFloat(dividerHeightPercent);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
deleted file mode 100644
index 9e49b51e1504..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ /dev/null
@@ -1,260 +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.windowdecor;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.os.Handler;
-import android.view.Choreographer;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
-import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.transition.Transitions;
-
-/**
- * View model for the window decoration with a caption and shadows. Works with
- * {@link CaptionWindowDecoration}.
- */
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
- private final ActivityTaskManager mActivityTaskManager;
- private final ShellTaskOrganizer mTaskOrganizer;
- private final Context mContext;
- private final Handler mMainHandler;
- private final Choreographer mMainChoreographer;
- private final DisplayController mDisplayController;
- private final SyncTransactionQueue mSyncQueue;
- private FreeformTaskTransitionStarter mTransitionStarter;
- private DesktopModeController mDesktopModeController;
-
- public CaptionWindowDecorViewModel(
- Context context,
- Handler mainHandler,
- Choreographer mainChoreographer,
- ShellTaskOrganizer taskOrganizer,
- DisplayController displayController,
- SyncTransactionQueue syncQueue,
- DesktopModeController desktopModeController) {
- mContext = context;
- mMainHandler = mainHandler;
- mMainChoreographer = mainChoreographer;
- mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
- mTaskOrganizer = taskOrganizer;
- mDisplayController = displayController;
- mSyncQueue = syncQueue;
- mDesktopModeController = desktopModeController;
- }
-
- @Override
- public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
- mTransitionStarter = transitionStarter;
- }
-
- @Override
- public CaptionWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- if (!shouldShowWindowDecor(taskInfo)) return null;
- final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- taskSurface,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
- CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, taskPositioner);
- windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
- setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
- setupCaptionColor(taskInfo, windowDecoration);
- return windowDecoration;
- }
-
- @Override
- public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
- if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
- final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
- if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
- return null;
- }
- return captionWindowDecor;
- }
-
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- decoration.relayout(taskInfo);
-
- setupCaptionColor(taskInfo, decoration);
- }
-
- private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
- }
-
- @Override
- public void setupWindowDecorationForTransition(
- RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- CaptionWindowDecoration decoration) {
- decoration.relayout(taskInfo, startT, finishT);
- }
-
- private class CaptionTouchEventListener implements
- View.OnClickListener, View.OnTouchListener {
-
- private final int mTaskId;
- private final WindowContainerToken mTaskToken;
- private final DragResizeCallback mDragResizeCallback;
-
- private int mDragPointerId = -1;
-
- private CaptionTouchEventListener(
- RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
- mTaskId = taskInfo.taskId;
- mTaskToken = taskInfo.token;
- mDragResizeCallback = dragResizeCallback;
- }
-
- @Override
- public void onClick(View v) {
- final int id = v.getId();
- if (id == R.id.close_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mTaskToken);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startRemoveTransition(wct);
- } else {
- mSyncQueue.queue(wct);
- }
- } else if (id == R.id.maximize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
- int displayWindowingMode =
- taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
- wct.setWindowingMode(mTaskToken,
- targetWindowingMode == displayWindowingMode
- ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
- if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- wct.setBounds(mTaskToken, null);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct);
- } else {
- mSyncQueue.queue(wct);
- }
- } else if (id == R.id.minimize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, false);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startMinimizedModeTransition(wct);
- } else {
- mSyncQueue.queue(wct);
- }
- }
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent e) {
- if (v.getId() != R.id.caption) {
- return false;
- }
- handleEventForMove(e);
-
- if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return false;
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, true /* onTop */);
- mSyncQueue.queue(wct);
- return true;
- }
-
- private void handleEventForMove(MotionEvent e) {
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int windowingMode = mDesktopModeController
- .getDisplayAreaWindowingMode(taskInfo.displayId);
- if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- return;
- }
- switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragPointerId = e.getPointerId(0);
- mDragResizeCallback.onDragResizeStart(
- 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
- case MotionEvent.ACTION_MOVE: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragResizeCallback.onDragResizeMove(
- e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
- }
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL: {
- int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
- .stableInsets().top;
- mDragResizeCallback.onDragResizeEnd(
- e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- if (e.getRawY(dragPointerIdx) <= statusBarHeight
- && windowingMode == WINDOWING_MODE_FREEFORM) {
- mDesktopModeController.setDesktopModeActive(false);
- }
- break;
- }
- }
- }
- }
-
- private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopModeStatus.IS_SUPPORTED
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && mDisplayController.getDisplayContext(taskInfo.displayId)
- .getResources().getConfiguration().smallestScreenWidthDp >= 600;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
deleted file mode 100644
index 733f6b7d5dbf..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ /dev/null
@@ -1,217 +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.windowdecor;
-
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.VectorDrawable;
-import android.os.Handler;
-import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
-
-/**
- * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
- *
- * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
- * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
- * caption buttons according to the luminance of the background.
- *
- * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
- */
-public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
- // The thickness of shadows of a window that has focus in DIP.
- private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
- // The thickness of shadows of a window that doesn't have focus in DIP.
- private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
-
- // Height of button (32dp) + 2 * margin (5dp each)
- private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
- private static final int RESIZE_HANDLE_IN_DIP = 30;
-
- private static final Rect EMPTY_OUTSET = new Rect();
- private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
- RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
-
- private final Handler mHandler;
- private final Choreographer mChoreographer;
- private final SyncTransactionQueue mSyncQueue;
-
- private View.OnClickListener mOnCaptionButtonClickListener;
- private View.OnTouchListener mOnCaptionTouchListener;
- private DragResizeCallback mDragResizeCallback;
-
- private DragResizeInputListener mDragResizeListener;
-
- private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
- new WindowDecoration.RelayoutResult<>();
-
- CaptionWindowDecoration(
- Context context,
- DisplayController displayController,
- ShellTaskOrganizer taskOrganizer,
- ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
- Handler handler,
- Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface);
-
- mHandler = handler;
- mChoreographer = choreographer;
- mSyncQueue = syncQueue;
- }
-
- void setCaptionListeners(
- View.OnClickListener onCaptionButtonClickListener,
- View.OnTouchListener onCaptionTouchListener) {
- mOnCaptionButtonClickListener = onCaptionButtonClickListener;
- mOnCaptionTouchListener = onCaptionTouchListener;
- }
-
- void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
- mDragResizeCallback = dragResizeCallback;
- }
-
- @Override
- void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- relayout(taskInfo, t, t);
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
- }
-
- void relayout(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final int shadowRadiusDp = taskInfo.isFocused
- ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
- final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
- final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
-
- WindowDecorLinearLayout oldRootView = mResult.mRootView;
- final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, startT, finishT, wct, mResult);
- taskInfo = null; // Clear it just in case we use it accidentally
-
- mTaskOrganizer.applyTransaction(wct);
-
- if (mResult.mRootView == null) {
- // This means something blocks the window decor from showing, e.g. the task is hidden.
- // Nothing is set up in this case including the decoration surface.
- return;
- }
- if (oldRootView != mResult.mRootView) {
- setupRootView();
- }
-
- if (!isDragResizeable) {
- closeDragResizeListener();
- return;
- }
-
- if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
- closeDragResizeListener();
- mDragResizeListener = new DragResizeInputListener(
- mContext,
- mHandler,
- mChoreographer,
- mDisplay.getDisplayId(),
- mDecorationContainerSurface,
- mDragResizeCallback);
- }
-
- mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
- }
-
- /**
- * Sets up listeners when a new root view is created.
- */
- private void setupRootView() {
- View caption = mResult.mRootView.findViewById(R.id.caption);
- caption.setOnTouchListener(mOnCaptionTouchListener);
- View maximize = caption.findViewById(R.id.maximize_window);
- if (DesktopModeStatus.IS_SUPPORTED) {
- // Hide maximize button when desktop mode is available
- maximize.setVisibility(View.GONE);
- } else {
- maximize.setVisibility(View.VISIBLE);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
- }
- View close = caption.findViewById(R.id.close_window);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- View minimize = caption.findViewById(R.id.minimize_window);
- minimize.setOnClickListener(mOnCaptionButtonClickListener);
- }
-
- void setCaptionColor(int captionColor) {
- if (mResult.mRootView == null) {
- return;
- }
-
- View caption = mResult.mRootView.findViewById(R.id.caption);
- GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
- captionDrawable.setColor(captionColor);
-
- int buttonTintColorRes =
- Color.valueOf(captionColor).luminance() < 0.5
- ? R.color.decor_button_light_color
- : R.color.decor_button_dark_color;
- ColorStateList buttonTintColor =
- caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- View maximize = caption.findViewById(R.id.maximize_window);
- VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
- maximizeBackground.setTintList(buttonTintColor);
-
- View close = caption.findViewById(R.id.close_window);
- VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
- closeBackground.setTintList(buttonTintColor);
- }
-
- private void closeDragResizeListener() {
- if (mDragResizeListener == null) {
- return;
- }
- mDragResizeListener.close();
- mDragResizeListener = null;
- }
-
- @Override
- public void close() {
- closeDragResizeListener();
- super.close();
- }
-}
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
new file mode 100644
index 000000000000..00aab6733369
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -0,0 +1,616 @@
+/*
+ * 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.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * View model for the window decoration with a caption and shadows. Works with
+ * {@link DesktopModeWindowDecoration}.
+ */
+
+public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
+ private static final String TAG = "DesktopModeWindowDecorViewModel";
+ private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
+ private final ActivityTaskManager mActivityTaskManager;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Context mContext;
+ private final Handler mMainHandler;
+ private final Choreographer mMainChoreographer;
+ private final DisplayController mDisplayController;
+ private final SyncTransactionQueue mSyncQueue;
+ private FreeformTaskTransitionStarter mTransitionStarter;
+ private Optional<DesktopModeController> mDesktopModeController;
+ private Optional<DesktopTasksController> mDesktopTasksController;
+ private boolean mTransitionDragActive;
+
+ private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+
+ private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
+ new SparseArray<>();
+ private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
+ private InputMonitorFactory mInputMonitorFactory;
+
+ public DesktopModeWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue,
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController) {
+ this(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController,
+ desktopTasksController,
+ new DesktopModeWindowDecoration.Factory(),
+ new InputMonitorFactory());
+ }
+
+ @VisibleForTesting
+ DesktopModeWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue,
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController,
+ DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
+ InputMonitorFactory inputMonitorFactory) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mMainChoreographer = mainChoreographer;
+ mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+ mTaskOrganizer = taskOrganizer;
+ mDisplayController = displayController;
+ mSyncQueue = syncQueue;
+ mDesktopModeController = desktopModeController;
+ mDesktopTasksController = desktopTasksController;
+
+ mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
+ mInputMonitorFactory = inputMonitorFactory;
+ }
+
+ @Override
+ public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
+ mTransitionStarter = transitionStarter;
+ }
+
+ @Override
+ public boolean onTaskOpening(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ if (!shouldShowWindowDecor(taskInfo)) return false;
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ return true;
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
+ final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
+
+ if (taskInfo.displayId != oldTaskInfo.displayId) {
+ removeTaskFromEventReceiver(oldTaskInfo.displayId);
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
+
+ decoration.relayout(taskInfo);
+ }
+
+ @Override
+ public void onTaskChanging(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+ if (!shouldShowWindowDecor(taskInfo)) {
+ if (decoration != null) {
+ destroyWindowDecoration(taskInfo);
+ }
+ return;
+ }
+
+ if (decoration == null) {
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ } else {
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+ }
+
+ @Override
+ public void onTaskClosing(
+ RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+
+ @Override
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ final DesktopModeWindowDecoration decoration =
+ mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.close();
+ int displayId = taskInfo.displayId;
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ removeTaskFromEventReceiver(displayId);
+ }
+ }
+
+ private class CaptionTouchEventListener implements
+ View.OnClickListener, View.OnTouchListener {
+
+ private final int mTaskId;
+ private final WindowContainerToken mTaskToken;
+ private final DragResizeCallback mDragResizeCallback;
+ private final DragDetector mDragDetector;
+
+ private int mDragPointerId = -1;
+
+ private CaptionTouchEventListener(
+ RunningTaskInfo taskInfo,
+ DragResizeCallback dragResizeCallback,
+ DragDetector dragDetector) {
+ mTaskId = taskInfo.taskId;
+ mTaskToken = taskInfo.token;
+ mDragResizeCallback = dragResizeCallback;
+ mDragDetector = dragDetector;
+ }
+
+ @Override
+ public void onClick(View v) {
+ DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final int id = v.getId();
+ if (id == R.id.close_window) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(mTaskToken);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startRemoveTransition(wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
+ } else if (id == R.id.back_button) {
+ injectBackKey();
+ } else if (id == R.id.caption_handle) {
+ decoration.createHandleMenu();
+ } else if (id == R.id.desktop_button) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+ mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
+ decoration.closeHandleMenu();
+ } else if (id == R.id.fullscreen_button) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
+ decoration.closeHandleMenu();
+ decoration.setButtonVisibility(false);
+ }
+ }
+
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK,
+ 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD,
+ 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ boolean isDrag = false;
+ int id = v.getId();
+ if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
+ return false;
+ }
+ if (id == R.id.caption_handle) {
+ isDrag = mDragDetector.detectDragEvent(e);
+ handleEventForMove(e);
+ }
+ if (e.getAction() != MotionEvent.ACTION_DOWN) {
+ return isDrag;
+ }
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (taskInfo.isFocused) {
+ return isDrag;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ return true;
+ }
+
+ /**
+ * @param e {@link MotionEvent} to process
+ * @return {@code true} if a drag is happening; or {@code false} if it is not
+ */
+ private void handleEventForMove(MotionEvent e) {
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (DesktopModeStatus.isProto2Enabled()
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(
+ taskInfo.displayId)
+ == WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
+ mDragResizeCallback.onDragResizeStart(
+ 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragResizeCallback.onDragResizeMove(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
+ .stableInsets().top;
+ mDragResizeCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // Switch a single task to fullscreen
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToFullscreen(taskInfo));
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (DesktopModeStatus.isActive(mContext)) {
+ // Turn off desktop mode
+ mDesktopModeController.ifPresent(
+ c -> c.setDesktopModeActive(false));
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // InputEventReceiver to listen for touch input outside of caption bounds
+ class EventReceiver extends InputEventReceiver {
+ private InputMonitor mInputMonitor;
+ private int mTasksOnDisplay;
+ EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+ super(channel, looper);
+ mInputMonitor = inputMonitor;
+ mTasksOnDisplay = 1;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ if (event instanceof MotionEvent) {
+ handled = true;
+ DesktopModeWindowDecorViewModel.this
+ .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
+ }
+ finishInputEvent(event, handled);
+ }
+
+ @Override
+ public void dispose() {
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ super.dispose();
+ }
+
+ private void incrementTaskNumber() {
+ mTasksOnDisplay++;
+ }
+
+ private void decrementTaskNumber() {
+ mTasksOnDisplay--;
+ }
+
+ private int getTasksOnDisplay() {
+ return mTasksOnDisplay;
+ }
+ }
+
+ /**
+ * Check if an EventReceiver exists on a particular display.
+ * If it does, increment its task count. Otherwise, create one for that display.
+ * @param displayId the display to check against
+ */
+ private void incrementEventReceiverTasks(int displayId) {
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ eventReceiver.incrementTaskNumber();
+ } else {
+ createInputChannel(displayId);
+ }
+ }
+
+ // If all tasks on this display are gone, we don't need to monitor its input.
+ private void removeTaskFromEventReceiver(int displayId) {
+ if (!mEventReceiversByDisplay.contains(displayId)) return;
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ if (eventReceiver == null) return;
+ eventReceiver.decrementTaskNumber();
+ if (eventReceiver.getTasksOnDisplay() == 0) {
+ disposeInputChannel(displayId);
+ }
+ }
+
+ /**
+ * Handle MotionEvents relevant to focused task's caption that don't directly touch it
+ *
+ * @param ev the {@link MotionEvent} received by {@link EventReceiver}
+ */
+ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null
+ || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ handleCaptionThroughStatusBar(ev);
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (!DesktopModeStatus.isActive(mContext)) {
+ handleCaptionThroughStatusBar(ev);
+ }
+ }
+ handleEventOutsideFocusedCaption(ev);
+ // Prevent status bar from reacting to a caption drag.
+ if (DesktopModeStatus.isProto2Enabled()) {
+ if (mTransitionDragActive) {
+ inputMonitor.pilferPointers();
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+ inputMonitor.pilferPointers();
+ }
+ }
+ }
+
+ // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
+ private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ return;
+ }
+
+ if (!mTransitionDragActive) {
+ focusedDecor.closeHandleMenuIfNeeded(ev);
+ }
+ }
+ }
+
+
+ /**
+ * Perform caption actions if not able to through normal means.
+ * Turn on desktop mode if handle is dragged below status bar.
+ */
+ private void handleCaptionThroughStatusBar(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Begin drag through status bar if applicable.
+ DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor != null) {
+ boolean dragFromStatusBarAllowed = false;
+ if (DesktopModeStatus.isProto2Enabled()) {
+ // In proto2 any full screen task can be dragged to freeform
+ dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN;
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ // In proto1 task can be dragged to freeform when not in desktop mode
+ dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
+ }
+
+ if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
+ mTransitionDragActive = true;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ mTransitionDragActive = false;
+ return;
+ }
+ if (mTransitionDragActive) {
+ mTransitionDragActive = false;
+ int statusBarHeight = mDisplayController
+ .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+ if (ev.getY() > statusBarHeight) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToDesktop(focusedDecor.mTaskInfo));
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+ }
+
+ return;
+ }
+ }
+ focusedDecor.checkClickEvent(ev);
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mTransitionDragActive = false;
+ }
+ }
+ }
+
+ @Nullable
+ private DesktopModeWindowDecoration getFocusedDecor() {
+ int size = mWindowDecorByTaskId.size();
+ DesktopModeWindowDecoration focusedDecor = null;
+ for (int i = 0; i < size; i++) {
+ DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ if (decor != null && decor.isFocused()) {
+ focusedDecor = decor;
+ break;
+ }
+ }
+ return focusedDecor;
+ }
+
+ private void createInputChannel(int displayId) {
+ InputManager inputManager = InputManager.getInstance();
+ InputMonitor inputMonitor =
+ mInputMonitorFactory.create(inputManager, mContext);
+ EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ inputMonitor.getInputChannel(), Looper.myLooper());
+ mEventReceiversByDisplay.put(displayId, eventReceiver);
+ }
+
+ private void disposeInputChannel(int displayId) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ if (eventReceiver != null) {
+ eventReceiver.dispose();
+ }
+ }
+
+ private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
+ return DesktopModeStatus.isAnyEnabled()
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && mDisplayController.getDisplayContext(taskInfo.displayId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ }
+
+ private void createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
+ final DesktopModeWindowDecoration windowDecoration =
+ mDesktopModeWindowDecorFactory.create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
+ TaskPositioner taskPositioner =
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+ CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(
+ taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.relayout(taskInfo, startT, finishT);
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
+ @Override
+ public void onDragStart(int taskId) {
+ mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ }
+ }
+
+ static class InputMonitorFactory {
+ InputMonitor create(InputManager inputManager, Context context) {
+ return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ }
+ }
+}
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
new file mode 100644
index 000000000000..9c2beb9c4b2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -0,0 +1,447 @@
+/*
+ * 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.windowdecor;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.VectorDrawable;
+import android.os.Handler;
+import android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+
+/**
+ * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
+ * {@link DesktopModeWindowDecorViewModel}. The caption bar contains a handle, back button, and
+ * close button.
+ *
+ * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
+ */
+public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
+ private final Handler mHandler;
+ private final Choreographer mChoreographer;
+ private final SyncTransactionQueue mSyncQueue;
+
+ private View.OnClickListener mOnCaptionButtonClickListener;
+ private View.OnTouchListener mOnCaptionTouchListener;
+ private DragResizeCallback mDragResizeCallback;
+
+ private DragResizeInputListener mDragResizeListener;
+
+ private RelayoutParams mRelayoutParams = new RelayoutParams();
+ private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
+ new WindowDecoration.RelayoutResult<>();
+
+ private boolean mDesktopActive;
+
+ private DragDetector mDragDetector;
+
+ private AdditionalWindow mHandleMenu;
+
+ DesktopModeWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+
+ mHandler = handler;
+ mChoreographer = choreographer;
+ mSyncQueue = syncQueue;
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
+ }
+
+ void setCaptionListeners(
+ View.OnClickListener onCaptionButtonClickListener,
+ View.OnTouchListener onCaptionTouchListener) {
+ mOnCaptionButtonClickListener = onCaptionButtonClickListener;
+ mOnCaptionTouchListener = onCaptionTouchListener;
+ }
+
+ void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
+ mDragResizeCallback = dragResizeCallback;
+ }
+
+ DragDetector getDragDetector() {
+ return mDragDetector;
+ }
+
+ @Override
+ void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ relayout(taskInfo, t, t);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+
+ void relayout(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+ final int shadowRadiusID = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
+
+ WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ int outsetLeftId = R.dimen.freeform_resize_handle;
+ int outsetTopId = R.dimen.freeform_resize_handle;
+ int outsetRightId = R.dimen.freeform_resize_handle;
+ int outsetBottomId = R.dimen.freeform_resize_handle;
+
+ mRelayoutParams.reset();
+ mRelayoutParams.mRunningTaskInfo = taskInfo;
+ mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
+ mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+ mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = shadowRadiusID;
+ if (isDragResizeable) {
+ mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
+ }
+ final Resources resources = mDecorWindowContext.getResources();
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int captionHeight = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionWidthId);
+ final int captionLeft = taskBounds.width() / 2
+ - captionWidth / 2;
+ final int captionTop = taskBounds.top
+ <= captionHeight / 2 ? 0 : -captionHeight / 2;
+ mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
+ relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
+
+ mTaskOrganizer.applyTransaction(wct);
+
+ if (mResult.mRootView == null) {
+ // This means something blocks the window decor from showing, e.g. the task is hidden.
+ // Nothing is set up in this case including the decoration surface.
+ return;
+ }
+ if (oldRootView != mResult.mRootView) {
+ setupRootView();
+ }
+
+ // If this task is not focused, do not show caption.
+ setCaptionVisibility(mTaskInfo.isFocused);
+
+ if (mTaskInfo.isFocused) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ updateButtonVisibility();
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ // Only handle should show if Desktop Mode is inactive.
+ boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
+ if (mDesktopActive != desktopCurrentStatus) {
+ mDesktopActive = desktopCurrentStatus;
+ setButtonVisibility(mDesktopActive);
+ }
+ }
+ }
+
+ if (!isDragResizeable) {
+ closeDragResizeListener();
+ return;
+ }
+
+ if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
+ closeDragResizeListener();
+ mDragResizeListener = new DragResizeInputListener(
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
+ }
+
+ int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ mDragDetector.setTouchSlop(touchSlop);
+
+ int resize_handle = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ int resize_corner = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_corner);
+ mDragResizeListener.setGeometry(
+ mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+ }
+
+ /**
+ * Sets up listeners when a new root view is created.
+ */
+ private void setupRootView() {
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ caption.setOnTouchListener(mOnCaptionTouchListener);
+ View close = caption.findViewById(R.id.close_window);
+ close.setOnClickListener(mOnCaptionButtonClickListener);
+ View back = caption.findViewById(R.id.back_button);
+ back.setOnClickListener(mOnCaptionButtonClickListener);
+ View handle = caption.findViewById(R.id.caption_handle);
+ handle.setOnTouchListener(mOnCaptionTouchListener);
+ handle.setOnClickListener(mOnCaptionButtonClickListener);
+ updateButtonVisibility();
+ }
+
+ private void setupHandleMenu() {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+ View desktop = menu.findViewById(R.id.desktop_button);
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ View split = menu.findViewById(R.id.split_screen_button);
+ split.setOnClickListener(mOnCaptionButtonClickListener);
+ View more = menu.findViewById(R.id.more_button);
+ more.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
+ /**
+ * Sets caption visibility based on task focus.
+ *
+ * @param visible whether or not the caption should be visible
+ */
+ private void setCaptionVisibility(boolean visible) {
+ int v = visible ? View.VISIBLE : View.GONE;
+ View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ captionView.setVisibility(v);
+ if (!visible) closeHandleMenu();
+ }
+
+ /**
+ * Sets the visibility of buttons and color of caption based on desktop mode status
+ */
+ void updateButtonVisibility() {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
+ setButtonVisibility(mDesktopActive);
+ }
+ }
+
+ /**
+ * Show or hide buttons
+ */
+ void setButtonVisibility(boolean visible) {
+ int visibility = visible ? View.VISIBLE : View.GONE;
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ View back = caption.findViewById(R.id.back_button);
+ View close = caption.findViewById(R.id.close_window);
+ back.setVisibility(visibility);
+ close.setVisibility(visibility);
+ int buttonTintColorRes =
+ mDesktopActive ? R.color.decor_button_dark_color
+ : R.color.decor_button_light_color;
+ ColorStateList buttonTintColor =
+ caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+ View handle = caption.findViewById(R.id.caption_handle);
+ VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
+ handleBackground.setTintList(buttonTintColor);
+ caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
+ }
+
+ boolean isHandleMenuActive() {
+ return mHandleMenu != null;
+ }
+
+ private void closeDragResizeListener() {
+ if (mDragResizeListener == null) {
+ return;
+ }
+ mDragResizeListener.close();
+ mDragResizeListener = null;
+ }
+
+ /**
+ * Create and display handle menu window
+ */
+ void createHandleMenu() {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String namePrefix = "Caption Menu";
+ mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
+ x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+ width, height);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ setupHandleMenu();
+ }
+
+ /**
+ * Close the handle menu window
+ */
+ void closeHandleMenu() {
+ if (!isHandleMenuActive()) return;
+ mHandleMenu.releaseView();
+ mHandleMenu = null;
+ }
+
+ @Override
+ void releaseViews() {
+ closeHandleMenu();
+ super.releaseViews();
+ }
+
+ /**
+ * Close an open handle menu if input is outside of menu coordinates
+ *
+ * @param ev the tapped point to compare against
+ */
+ void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (isHandleMenuActive()) {
+ if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) {
+ closeHandleMenu();
+ }
+ }
+ }
+
+ boolean isFocused() {
+ return mTaskInfo.isFocused;
+ }
+
+ /**
+ * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+ *
+ * @param ev the {@link MotionEvent} to offset
+ * @return the point of the input in local space
+ */
+ private PointF offsetCaptionLocation(MotionEvent ev) {
+ PointF result = new PointF(ev.getX(), ev.getY());
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ result.offset(-positionInParent.x, -positionInParent.y);
+ return result;
+ }
+
+ /**
+ * Determine if a passed MotionEvent is in a view in caption
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @param layoutId the id of the view
+ * @return {@code true} if event is inside the specified view, {@code false} if not
+ */
+ private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
+ if (mResult.mRootView == null) return false;
+ PointF inputPoint = offsetCaptionLocation(ev);
+ View view = mResult.mRootView.findViewById(layoutId);
+ return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+ }
+
+ boolean checkTouchEventInHandle(MotionEvent ev) {
+ if (isHandleMenuActive()) return false;
+ return checkEventInCaptionView(ev, R.id.caption_handle);
+ }
+
+ /**
+ * Check a passed MotionEvent if a click has occurred on any button on this caption
+ * Note this should only be called when a regular onClick is not possible
+ * (i.e. the button was clicked through status bar layer)
+ *
+ * @param ev the MotionEvent to compare
+ */
+ void checkClickEvent(MotionEvent ev) {
+ if (mResult.mRootView == null) return;
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ PointF inputPoint = offsetCaptionLocation(ev);
+ if (!isHandleMenuActive()) {
+ View handle = caption.findViewById(R.id.caption_handle);
+ clickIfPointInView(inputPoint, handle);
+ } else {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ if (clickIfPointInView(inputPoint, fullscreen)) return;
+ View desktop = menu.findViewById(R.id.desktop_button);
+ if (clickIfPointInView(inputPoint, desktop)) return;
+ View split = menu.findViewById(R.id.split_screen_button);
+ if (clickIfPointInView(inputPoint, split)) return;
+ View more = menu.findViewById(R.id.more_button);
+ clickIfPointInView(inputPoint, more);
+ }
+ }
+
+ private boolean clickIfPointInView(PointF inputPoint, View v) {
+ if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+ mOnCaptionButtonClickListener.onClick(v);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {
+ closeDragResizeListener();
+ closeHandleMenu();
+ super.close();
+ }
+
+ static class Factory {
+
+ DesktopModeWindowDecoration create(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ return new DesktopModeWindowDecoration(
+ context,
+ displayController,
+ taskOrganizer,
+ taskInfo,
+ taskSurface,
+ handler,
+ choreographer,
+ syncQueue);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 000000000000..0abe8ab2e30b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.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.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+ private int mTouchSlop;
+ private PointF mInputDownPoint;
+ private boolean mIsDragEvent;
+ private int mDragPointerId;
+ public DragDetector(int touchSlop) {
+ mTouchSlop = touchSlop;
+ mInputDownPoint = new PointF();
+ mIsDragEvent = false;
+ mDragPointerId = -1;
+ }
+
+ /**
+ * Determine if {@link MotionEvent} is part of a drag event.
+ * @return {@code true} if this is a drag event, {@code false} if not
+ */
+ public boolean detectDragEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case ACTION_DOWN: {
+ mDragPointerId = ev.getPointerId(0);
+ float rawX = ev.getRawX(0);
+ float rawY = ev.getRawY(0);
+ mInputDownPoint.set(rawX, rawY);
+ return false;
+ }
+ case ACTION_MOVE: {
+ if (!mIsDragEvent) {
+ int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+ float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ if (Math.hypot(dx, dy) > mTouchSlop) {
+ mIsDragEvent = true;
+ }
+ }
+ return mIsDragEvent;
+ }
+ case ACTION_UP: {
+ boolean result = mIsDragEvent;
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return result;
+ }
+ case ACTION_CANCEL: {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return false;
+ }
+ }
+ return mIsDragEvent;
+ }
+
+ public void setTouchSlop(int touchSlop) {
+ mTouchSlop = touchSlop;
+ }
+}
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 3d014959a952..d3f1332f6224 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -36,14 +37,18 @@ import android.view.InputEventReceiver;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import com.android.internal.view.BaseIWindow;
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
- * convert them to drag resize requests.
+ * and corners. Converts them to drag resize requests.
+ * Task edges are for resizing with a mouse.
+ * Task corners are for resizing with touch input.
*/
+// TODO(b/251270585): investigate how to pass taps in corners to the tasks
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
@@ -63,8 +68,15 @@ class DragResizeInputListener implements AutoCloseable {
private int mWidth;
private int mHeight;
private int mResizeHandleThickness;
+ private int mCornerSize;
+
+ private Rect mLeftTopCornerBounds;
+ private Rect mRightTopCornerBounds;
+ private Rect mLeftBottomCornerBounds;
+ private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
+ private DragDetector mDragDetector;
DragResizeInputListener(
Context context,
@@ -103,6 +115,7 @@ class DragResizeInputListener implements AutoCloseable {
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -118,16 +131,23 @@ class DragResizeInputListener implements AutoCloseable {
* @param height The height of the drag resize handler in pixels, including resize handle
* thickness. That is task height + 2 * resize handle thickness.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
+ * @param cornerSize The size of the resize handle centered in each corner.
+ * @param touchSlop The distance in pixels user has to drag with touch for it to register as
+ * a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness) {
+ void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ int touchSlop) {
if (mWidth == width && mHeight == height
- && mResizeHandleThickness == resizeHandleThickness) {
+ && mResizeHandleThickness == resizeHandleThickness
+ && mCornerSize == cornerSize) {
return;
}
mWidth = width;
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
+ mCornerSize = cornerSize;
+ mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -146,6 +166,40 @@ class DragResizeInputListener implements AutoCloseable {
mWidth, mHeight);
touchRegion.union(bottomInputBounds);
+ // Set up touch areas in each corner.
+ int cornerRadius = mCornerSize / 2;
+ mLeftTopCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftTopCornerBounds);
+
+ mRightTopCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightTopCornerBounds);
+
+ mLeftBottomCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftBottomCornerBounds);
+
+ mRightBottomCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightBottomCornerBounds);
+
try {
mWindowSession.updateInputChannel(
mInputChannel.getToken(),
@@ -173,6 +227,8 @@ class DragResizeInputListener implements AutoCloseable {
private final Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
+ private boolean mShouldHandleEvents;
+ private boolean mDragging;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -216,41 +272,94 @@ class DragResizeInputListener implements AutoCloseable {
}
MotionEvent e = (MotionEvent) inputEvent;
+ boolean result = false;
+ // Check if this is a touch event vs mouse event.
+ // Touch events are tracked in four corners. Other events are tracked in resize edges.
+ boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ if (isTouch) {
+ mDragging = mDragDetector.detectDragEvent(e);
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mDragPointerId = e.getPointerId(0);
- mCallback.onDragResizeStart(
- calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
+ float x = e.getX(0);
+ float y = e.getY(0);
+ if (isTouch) {
+ mShouldHandleEvents = isInCornerBounds(x, y);
+ } else {
+ mShouldHandleEvents = isInResizeHandleBounds(x, y);
+ }
+ if (mShouldHandleEvents) {
+ mDragPointerId = e.getPointerId(0);
+ float rawX = e.getRawX(0);
+ float rawY = e.getRawY(0);
+ int ctrlType = calculateCtrlType(isTouch, x, y);
+ mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_MOVE: {
+ if (!mShouldHandleEvents) {
+ break;
+ }
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeMove(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ float rawX = e.getRawX(dragPointerIndex);
+ float rawY = e.getRawY(dragPointerIndex);
+ if (!isTouch) {
+ // For all other types allow immediate dragging.
+ mDragging = true;
+ }
+ if (mDragging) {
+ mCallback.onDragResizeMove(rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeEnd(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ if (mShouldHandleEvents && mDragging) {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ }
+ mDragging = false;
+ mShouldHandleEvents = false;
mDragPointerId = -1;
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
+ result = true;
break;
}
- return true;
+ return result;
+ }
+
+ private boolean isInCornerBounds(float xf, float yf) {
+ return calculateCornersCtrlType(xf, yf) != 0;
+ }
+
+ private boolean isInResizeHandleBounds(float x, float y) {
+ return calculateResizeHandlesCtrlType(x, y) != 0;
+ }
+
+ @TaskPositioner.CtrlType
+ private int calculateCtrlType(boolean isTouch, float x, float y) {
+ if (isTouch) {
+ return calculateCornersCtrlType(x, y);
+ }
+ return calculateResizeHandlesCtrlType(x, y);
}
@TaskPositioner.CtrlType
- private int calculateCtrlType(float x, float y) {
+ private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
if (x < mResizeHandleThickness) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
@@ -267,8 +376,27 @@ class DragResizeInputListener implements AutoCloseable {
return ctrlType;
}
+ @TaskPositioner.CtrlType
+ private int calculateCornersCtrlType(float x, float y) {
+ int xi = (int) x;
+ int yi = (int) y;
+ if (mLeftTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mLeftBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ if (mRightTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mRightBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ return 0;
+ }
+
private void updateCursorType(float x, float y) {
- @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
+ @TaskPositioner.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
@@ -292,4 +420,4 @@ class DragResizeInputListener implements AutoCloseable {
mInputManager.setPointerIconType(cursorType);
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 280569b05d87..a49a300995e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -25,9 +25,10 @@ import com.android.wm.shell.ShellTaskOrganizer;
class TaskPositioner implements DragResizeCallback {
- @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
@interface CtrlType {}
+ static final int CTRL_TYPE_UNDEFINED = 0;
static final int CTRL_TYPE_LEFT = 1;
static final int CTRL_TYPE_RIGHT = 2;
static final int CTRL_TYPE_TOP = 4;
@@ -39,16 +40,29 @@ class TaskPositioner implements DragResizeCallback {
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
+ // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+ // Used to optimized fluid resizing of freeform tasks.
+ private boolean mPendingDragResizeHint = false;
private int mCtrlType;
+ private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDragStartListener = dragStartListener;
}
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ if (ctrlType != CTRL_TYPE_UNDEFINED) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ mPendingDragResizeHint = true;
+ }
+
+ mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
@@ -58,19 +72,31 @@ class TaskPositioner implements DragResizeCallback {
@Override
public void onDragResizeMove(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (changeBounds(wct, x, y)) {
+ if (mPendingDragResizeHint) {
+ // This is the first bounds change since drag resize operation started.
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+ mPendingDragResizeHint = false;
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
@Override
public void onDragResizeEnd(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
mCtrlType = 0;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
+ mPendingDragResizeHint = false;
}
- private void changeBounds(float x, float y) {
+ private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
float deltaX = x - mResizeStartPoint.x;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -91,9 +117,17 @@ class TaskPositioner implements DragResizeCallback {
}
if (!mResizeTaskBounds.isEmpty()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ return true;
}
+ return false;
+ }
+
+ interface DragStartListener {
+ /**
+ * Inform the implementing class that a drag resize has started
+ * @param taskId id of this positioner's {@link WindowDecoration}
+ */
+ void onDragStart(int taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d9697d288ab6..907977c661f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -19,8 +19,6 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
import android.view.SurfaceControl;
-import androidx.annotation.Nullable;
-
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
/**
@@ -28,11 +26,8 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
* customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
* interactions with UI widgets in window decorations and send corresponding requests to system
* servers.
- *
- * @param <T> The actual decoration type
*/
-public interface WindowDecorViewModel<T extends AutoCloseable> {
-
+public interface WindowDecorViewModel {
/**
* Sets the transition starter that starts freeform task transitions.
*
@@ -41,50 +36,62 @@ public interface WindowDecorViewModel<T extends AutoCloseable> {
void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
/**
- * Creates a window decoration for the given task.
- * Can be {@code null} for Fullscreen tasks but not Freeform ones.
+ * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
+ * not Freeform ones.
*
- * @param taskInfo the initial task info of the task
+ * @param taskInfo the initial task info of the task
* @param taskSurface the surface of the task
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
- * @return the window decoration object
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
+ * @return {@code true} if window decoration was created, {@code false} otherwise
*/
- @Nullable T createWindowDecoration(
+ boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
/**
- * Adopts the window decoration if possible.
- * May be {@code null} if a window decor is not needed or the given one is incompatible.
+ * Notifies a task info update on the given task, with the window decoration created previously
+ * for this task by {@link #onTaskOpening}.
*
- * @param windowDecor the potential window decoration to adopt
- * @return the window decoration if it can be adopted, or {@code null} otherwise.
+ * @param taskInfo the new task info of the task
*/
- @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
+ void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
/**
- * Notifies a task info update on the given task, with the window decoration created previously
- * for this task by {@link #createWindowDecoration}.
+ * Notifies a transition is about to start about the given task to give the window decoration a
+ * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates
+ * a window decoration if one does not exist but is required.
*
- * @param taskInfo the new task info of the task
- * @param windowDecoration the window decoration created for the task
+ * @param taskInfo the initial task info of the task
+ * @param taskSurface the surface of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
*/
- void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+ void onTaskChanging(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT);
/**
- * Notifies a transition is about to start about the given task to give the window decoration a
- * chance to prepare for this transition.
+ * Notifies that the given task is about to close to give the window decoration a chance to
+ * prepare for this transition.
*
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
- * @param windowDecoration the window decoration created for the task
+ * @param taskInfo the initial task info of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
*/
- void setupWindowDecorationForTransition(
+ void onTaskClosing(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- T windowDecoration);
-}
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Destroys the window decoration of the give task.
+ *
+ * @param taskInfo the info of the task
+ */
+ void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 3e3a864f48c7..7f85988d1377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,11 +19,11 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.InsetsState;
import android.view.LayoutInflater;
@@ -33,6 +33,7 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -91,7 +92,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mTaskBackgroundSurface;
SurfaceControl mCaptionContainerSurface;
- private CaptionWindowManager mCaptionWindowManager;
+ private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private final Rect mCaptionInsetsRect = new Rect();
@@ -142,15 +143,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
- void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT, WindowContainerTransaction wct,
+ void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
- if (taskInfo != null) {
- mTaskInfo = taskInfo;
+ if (params.mRunningTaskInfo != null) {
+ mTaskInfo = params.mRunningTaskInfo;
}
if (!mTaskInfo.isVisible) {
@@ -159,7 +159,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
- if (rootView == null && layoutResId == 0) {
+ if (rootView == null && params.mLayoutResId == 0) {
throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
}
@@ -176,15 +176,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
- if (layoutResId != 0) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ if (params.mLayoutResId != 0) {
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId , null);
}
// DecorationContainerSurface
@@ -196,24 +196,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setParent(mTaskSurface)
.build();
- startT.setTrustedOverlay(mDecorationContainerSurface, true);
+ startT.setTrustedOverlay(mDecorationContainerSurface, true)
+ .setLayer(mDecorationContainerSurface,
+ TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
- final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+ final Resources resources = mDecorWindowContext.getResources();
+ outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
- + (int) (outsetsDp.right * outResult.mDensity)
- - decorContainerOffsetX;
+ + loadDimensionPixelSize(resources, params.mOutsetRightId)
+ - outResult.mDecorContainerOffsetX;
outResult.mHeight = taskBounds.height()
- + (int) (outsetsDp.bottom * outResult.mDensity)
- - decorContainerOffsetY;
+ + loadDimensionPixelSize(resources, params.mOutsetBottomId)
+ - outResult.mDecorContainerOffsetY;
startT.setPosition(
- mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
+ mDecorationContainerSurface,
+ outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
+ .setWindowCrop(mDecorationContainerSurface,
+ outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
// TaskBackgroundSurface
@@ -224,18 +226,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setEffectLayer()
.setParent(mTaskSurface)
.build();
+
+ startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
}
- float shadowRadius = outResult.mDensity * shadowRadiusDp;
+ float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
+ taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mTaskBackgroundSurface, -1)
.show(mTaskBackgroundSurface);
// CaptionContainerSurface, CaptionWindowManager
@@ -248,23 +251,30 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
+ final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
+ ? taskBounds.width()
+ : loadDimensionPixelSize(resources, params.mCaptionWidthId);
+
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX, -decorContainerOffsetY)
- .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+ mCaptionContainerSurface,
+ -outResult.mDecorContainerOffsetX + params.mCaptionX,
+ -outResult.mDecorContainerOffsetY + params.mCaptionY)
+ .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+ mCaptionWindowManager = new WindowlessWindowManager(
+ mTaskInfo.getConfiguration(), mCaptionContainerSurface,
+ null /* hostInputToken */);
}
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
+ new WindowManager.LayoutParams(captionWidth, captionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -282,8 +292,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
- wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
+ wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
+ CAPTION_INSETS_TYPES);
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -291,10 +303,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
mTaskSurfaceCrop.set(
- decorContainerOffsetX,
- decorContainerOffsetY,
- outResult.mWidth + decorContainerOffsetX,
- outResult.mHeight + decorContainerOffsetY);
+ outResult.mDecorContainerOffsetX,
+ outResult.mDecorContainerOffsetY,
+ outResult.mWidth + outResult.mDecorContainerOffsetX,
+ outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -315,7 +327,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return true;
}
- private void releaseViews() {
+ void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -358,34 +370,159 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
+ static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ static float loadDimension(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimension(resourceId);
+ }
+
+ /**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return
+ */
+ AdditionalWindow addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ SurfaceControl windowSurfaceControl = builder
+ .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mDecorationContainerSurface)
+ .build();
+ View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+ t.setPosition(
+ windowSurfaceControl, xPos, yPos)
+ .setWindowCrop(windowSurfaceControl, width, height)
+ .show(windowSurfaceControl);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ windowSurfaceControl, null /* hostInputToken */);
+ SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+ .create(mDecorWindowContext, mDisplay, windowManager);
+ viewHost.setView(v, lp);
+ return new AdditionalWindow(windowSurfaceControl, viewHost,
+ mSurfaceControlTransactionSupplier);
+ }
+
+ static class RelayoutParams{
+ RunningTaskInfo mRunningTaskInfo;
+ int mLayoutResId;
+ int mCaptionHeightId;
+ int mCaptionWidthId;
+ int mShadowRadiusId;
+
+ int mOutsetTopId;
+ int mOutsetBottomId;
+ int mOutsetLeftId;
+ int mOutsetRightId;
+
+ int mCaptionX;
+ int mCaptionY;
+
+ void setOutsets(int leftId, int topId, int rightId, int bottomId) {
+ mOutsetLeftId = leftId;
+ mOutsetTopId = topId;
+ mOutsetRightId = rightId;
+ mOutsetBottomId = bottomId;
+ }
+
+ void setCaptionPosition(int left, int top) {
+ mCaptionX = left;
+ mCaptionY = top;
+ }
+
+ void reset() {
+ mLayoutResId = Resources.ID_NULL;
+ mCaptionHeightId = Resources.ID_NULL;
+ mCaptionWidthId = Resources.ID_NULL;
+ mShadowRadiusId = Resources.ID_NULL;
+
+ mOutsetTopId = Resources.ID_NULL;
+ mOutsetBottomId = Resources.ID_NULL;
+ mOutsetLeftId = Resources.ID_NULL;
+ mOutsetRightId = Resources.ID_NULL;
+
+ mCaptionX = 0;
+ mCaptionY = 0;
+ }
+ }
+
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mWidth;
int mHeight;
- float mDensity;
T mRootView;
+ int mDecorContainerOffsetX;
+ int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
- mDensity = 0;
+ mDecorContainerOffsetX = 0;
+ mDecorContainerOffsetY = 0;
mRootView = null;
}
}
- private static class CaptionWindowManager extends WindowlessWindowManager {
- CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
- super(config, rootSurface, null /* hostInputToken */);
+ interface SurfaceControlViewHostFactory {
+ default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+ return new SurfaceControlViewHost(c, d, wmm);
}
+ }
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
+ /**
+ * Subclass for additional windows associated with this WindowDecoration
+ */
+ static class AdditionalWindow {
+ SurfaceControl mWindowSurface;
+ SurfaceControlViewHost mWindowViewHost;
+ Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ private AdditionalWindow(SurfaceControl surfaceControl,
+ SurfaceControlViewHost surfaceControlViewHost,
+ Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ mWindowSurface = surfaceControl;
+ mWindowViewHost = surfaceControlViewHost;
+ mTransactionSupplier = transactionSupplier;
}
- }
- interface SurfaceControlViewHostFactory {
- default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
- return new SurfaceControlViewHost(c, d, wmm);
+ void releaseView() {
+ WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+ if (mWindowViewHost != null) {
+ mWindowViewHost.release();
+ mWindowViewHost = null;
+ }
+ windowManager = null;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ boolean released = false;
+ if (mWindowSurface != null) {
+ t.remove(mWindowSurface);
+ mWindowSurface = null;
+ released = true;
+ }
+ if (released) {
+ t.apply();
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 1a8b9540cbd0..2ac1dc0c4838 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -69,6 +69,9 @@ android_test {
enabled: false,
},
+ platform_apis: true,
+ certificate: "platform",
+
aaptflags: [
"--extra-packages",
"com.android.wm.shell.tests",
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index 59d9104fb5ba..fac04614d945 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -19,6 +19,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.wm.shell.tests">
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+
<application android:debuggable="true" android:largeHeap="true">
<uses-library android:name="android.test.mock" />
<uses-library android:name="android.test.runner" />
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
new file mode 100644
index 000000000000..8949a75d1a15
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -0,0 +1,27 @@
+<?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>
+ <!-- Resources used in WindowDecorationTests -->
+ <dimen name="test_freeform_decor_caption_height">32dp</dimen>
+ <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+ <dimen name="test_window_decor_left_outset">10dp</dimen>
+ <dimen name="test_window_decor_top_outset">20dp</dimen>
+ <dimen name="test_window_decor_right_outset">30dp</dimen>
+ <dimen name="test_window_decor_bottom_outset">40dp</dimen>
+ <dimen name="test_window_decor_shadow_radius">5dp</dimen>
+ <dimen name="test_window_decor_resize_handle">10dp</dimen>
+</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5af48f..081c8ae91bdb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -61,8 +53,6 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
- @Test
- public void testPrepareClearBoundsForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- assertEquals(wct.getChanges().size(), 2);
- Change boundsChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(boundsChange1);
- assertNotEquals(
- (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
- Change boundsChange2 = wct.getChanges().get(token2.binder());
- assertNotNull(boundsChange2);
- assertNotEquals(
- (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
- }
-
- @Test
- public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- // Only clear bounds for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only task with freeform windowing mode and the right display should be updated
- assertEquals(wct.getChanges().size(), 1);
- Change wmModeChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(wmModeChange1);
- assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only clear freeform for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
- RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
- taskInfo.displayId = 1;
- taskInfo.token = token.token();
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- return taskInfo;
- }
-
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
-
- IBinder binder() {
- return mBinder;
- }
- }
}
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 98b59126227c..79070b1469be 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
@@ -40,6 +40,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+
/**
* Tests for {@link ActivityEmbeddingAnimationRunner}.
*
@@ -62,13 +64,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
final TransitionInfo.Change embeddingChange = createChange();
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
info.addChange(embeddingChange);
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
- finishCallback.capture());
+ finishCallback.capture(), any());
verify(mStartTransaction).apply();
verify(mAnimator).start();
verifyNoMoreInteractions(mFinishTransaction);
@@ -88,7 +90,8 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
info.addChange(embeddingChange);
final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction,
- () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+ () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+ new ArrayList());
// The animation should be empty when it is behind starting window.
assertEquals(0, animator.getDuration());
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 3792e8361284..54a12ab999c5 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
@@ -56,13 +56,12 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
@Mock
SurfaceControl.Transaction mFinishTransaction;
@Mock
- Transitions.TransitionFinishCallback mFinishCallback;
- @Mock
Animator mAnimator;
ActivityEmbeddingController mController;
ActivityEmbeddingAnimationRunner mAnimRunner;
ActivityEmbeddingAnimationSpec mAnimSpec;
+ Transitions.TransitionFinishCallback mFinishCallback;
@CallSuper
@Before
@@ -75,9 +74,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
assertNotNull(mAnimRunner);
mAnimSpec = mAnimRunner.mAnimationSpec;
assertNotNull(mAnimSpec);
+ mFinishCallback = (wct, wctCB) -> {};
spyOn(mController);
spyOn(mAnimRunner);
spyOn(mAnimSpec);
+ spyOn(mFinishCallback);
}
/** Creates a mock {@link TransitionInfo.Change}. */
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 baecf6fe6673..4d98b6ba4f7a 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
@@ -55,7 +55,7 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Before
public void setup() {
super.setUp();
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90a377309edd..2e328b0736dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,7 +63,9 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Ignore;
@@ -102,6 +104,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private IBackNaviAnimationController mIBackNaviAnimationController;
+ @Mock
+ private ShellController mShellController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -118,10 +123,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit,
+ mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ mController.setEnableUAnimation(true);
mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
@@ -175,6 +181,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -234,10 +246,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
// 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();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
+ verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -250,7 +262,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit,
+ mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
@@ -265,11 +277,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
- verify(appCallback, never()).onBackStarted();
+ verify(appCallback, never()).onBackStarted(any(BackEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted();
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
}
@@ -302,7 +314,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
}
@Test
@@ -321,7 +333,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
}
@@ -337,7 +349,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// 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();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 000000000000..1e0f9bc6322f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
new file mode 100644
index 000000000000..3aefc3f03a8a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static org.junit.Assert.assertEquals;
+
+import android.window.BackEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TouchTrackerTest {
+ private static final float FAKE_THRESHOLD = 400;
+ private static final float INITIAL_X_LEFT_EDGE = 5;
+ private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
+ private TouchTracker mTouchTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ mTouchTracker = new TouchTracker();
+ mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
+ }
+
+ @Test
+ public void generatesProgress_onStart() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ BackEvent event = mTouchTracker.createStartEvent(null);
+ assertEquals(event.getProgress(), 0f, 0f);
+ }
+
+ @Test
+ public void generatesProgress_leftEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ float touchX = 10;
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX += 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX += 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+ }
+
+ @Test
+ public void generatesProgress_rightEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
+ float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX -= 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX += 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+ }
+
+ private float getProgress() {
+ return mTouchTracker.createProgressEvent().getProgress();
+ }
+}
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 9967e5f47752..262ee72d86fc 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
@@ -22,6 +22,8 @@ import static android.view.Surface.ROTATION_0;
import static android.view.WindowInsets.Type.ime;
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.Mockito.mock;
@@ -131,6 +133,15 @@ public class DisplayImeControllerTest extends ShellTestCase {
verify(mT).show(any());
}
+ @Test
+ public void insetsControlChanged_updateImeSourceControl() {
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+ assertNotNull(mPerDisplay.mImeSourceControl);
+
+ mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[]{});
+ assertNull(mPerDisplay.mImeSourceControl);
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 695550dd8fa5..3d779481d361 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.google.common.truth.Truth.assertThat;
@@ -91,12 +92,21 @@ public class SplitLayoutTests extends ShellTestCase {
// Verify updateConfiguration returns true if the root bounds changed.
config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080));
assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if the orientation changed.
+ config.orientation = ORIENTATION_LANDSCAPE;
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if the density changed.
+ config.densityDpi = 123;
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
}
@Test
public void testUpdateDivideBounds() {
mSplitLayout.updateDivideBounds(anyInt());
- verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
+ anyInt());
}
@Test
@@ -131,7 +141,7 @@ public class SplitLayoutTests extends ShellTestCase {
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
- mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+ mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
}
@@ -143,7 +153,7 @@ public class SplitLayoutTests extends ShellTestCase {
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
- mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+ mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
}
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 6292130ddec9..2fc0914acbd4 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
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
@@ -93,6 +94,7 @@ public class CompatUIControllerTest extends ShellTestCase {
private @Mock Lazy<Transitions> mMockTransitionsLazy;
private @Mock CompatUIWindowManager mMockCompatLayout;
private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+ private @Mock DockStateReader mDockStateReader;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -113,7 +115,7 @@ public class CompatUIControllerTest extends ShellTestCase {
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
- mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
index 1dee88c43806..a58620dfc6dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
@@ -68,11 +68,11 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
@Test
public void testOnFinishInflate() {
- assertEquals(mLayout.getDialogContainer(),
+ assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_education_dialog_container));
assertEquals(mLayout.getDialogTitle(),
mLayout.findViewById(R.id.letterbox_education_dialog_title));
- assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground());
+ assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
assertEquals(mLayout.getBackground().getAlpha(), 0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index f3a8cf45b7f8..14190f18929c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -54,7 +54,9 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -97,12 +99,13 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Captor
private ArgumentCaptor<Runnable> mRunOnIdleCaptor;
- @Mock private LetterboxEduAnimationController mAnimationController;
+ @Mock private DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@Mock private Transitions mTransitions;
@Mock private Runnable mOnDismissCallback;
+ @Mock private DockStateReader mDockStateReader;
private SharedPreferences mSharedPreferences;
@Nullable
@@ -153,6 +156,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+ true, /* isDocked */ true);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -354,7 +367,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertThat(params.width).isEqualTo(expectedWidth);
assertThat(params.height).isEqualTo(expectedHeight);
MarginLayoutParams dialogParams =
- (MarginLayoutParams) layout.getDialogContainer().getLayoutParams();
+ (MarginLayoutParams) layout.getDialogContainerView().getLayoutParams();
int verticalMargin = (int) mContext.getResources().getDimension(
R.dimen.letterbox_education_dialog_margin);
assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin);
@@ -382,17 +395,27 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
}
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
+ return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
+ false, isDocked);
+ }
+
private LetterboxEduWindowManager createWindowManager(boolean eligible,
int userId, boolean isTaskbarEduShowing) {
+ return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible,
+ int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+ doReturn(isDocked).when(mDockStateReader).isDocked();
LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mTransitions, mOnDismissCallback,
- mAnimationController);
+ mAnimationController, mDockStateReader);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
-
return windowManager;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97d9199..08af3d3eecfe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -20,28 +20,38 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.window.DisplayAreaInfo;
-import android.window.WindowContainerToken;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.test.filters.SmallTest;
@@ -49,9 +59,9 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -63,11 +73,16 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeControllerTest extends ShellTestCase {
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -76,9 +91,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Mock
private Handler mMockHandler;
@Mock
- private Transitions mMockTransitions;
- private TestShellExecutor mExecutor;
-
+ private Transitions mTransitions;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
@@ -87,23 +100,21 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Before
public void setUp() {
mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
- mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockTransitions,
- mDesktopModeTaskRepository, mMockHandler, mExecutor);
+ mController = createController();
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
- new WindowContainerTransaction());
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
+ clearInvocations(mTransitions);
}
@After
@@ -113,149 +124,294 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Test
public void instantiate_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit).addInitCallback(any(), any());
}
@Test
- public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWct = new WindowContainerTransaction();
- MockToken taskMockToken = new MockToken();
- taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
+ public void instantiate_flagOff_doNotAddInitCallback() {
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
+ clearInvocations(mShellInit);
+
+ createController();
+
+ verify(mShellInit, never()).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // The test
mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to freeform
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ }
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ @Test
+ public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // WCT should have 2 changes - clear task wm mode and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(2);
+ mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to fullscreen
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ @Test
+ public void testDesktopModeEnabled_windowingModeCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 2 changes: Root TDA windowing mode and 1 task
+ assertThat(wct.getChanges().size()).isEqualTo(2);
+ // No changes for tasks that are not standard or freeform
+ assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard freeform task has windowing mode cleared
+ Change change = wct.getChanges().get(freeformTask.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
}
@Test
- public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
- MockToken taskWmMockToken = new MockToken();
- taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWmWct);
-
- // Create a fake WCT to simulate clearing task bounds
- WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
- MockToken taskBoundsMockToken = new MockToken();
- taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
+ public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // The test
mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 3 changes: Root TDA windowing mode and 2 tasks
+ assertThat(wct.getChanges().size()).isEqualTo(3);
+ // No changes to home task
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard tasks have bounds cleared
+ assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+ assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+ // Freeform standard tasks have windowing mode cleared
+ assertThat(wct.getChanges().get(
+ freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+ WINDOWING_MODE_UNDEFINED);
+ }
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
- // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(3);
-
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmMode).isNotNull();
- assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
- // Verify executed WCT has a change for clearing task bounds
- Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(bounds).isNotNull();
- assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
-
- // Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ @Test
+ public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+ createMockDisplayArea();
+ RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+ fullscreenTask1.isVisible = true;
+ RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+ fullscreenTask2.isVisible = false;
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // Check that there are hierarchy changes for home task and visible task
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // First show home task
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+ // Then visible task on top of it
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
- public void testShowDesktopApps() {
- // Set up two active tasks on desktop
- mDesktopModeTaskRepository.addActiveTask(1);
- mDesktopModeTaskRepository.addActiveTask(2);
- MockToken token1 = new MockToken();
- MockToken token2 = new MockToken();
- ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
- token1.token()).setLastActiveTime(100).build();
- ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
- token2.token()).setLastActiveTime(200).build();
- when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
- when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+ public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+ // Set up two active tasks on desktop, task2 is on top of task1.
+ RunningTaskInfo freeformTask1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask1.taskId, false /* visible */);
+ RunningTaskInfo freeformTask2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask2.taskId, false /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+ freeformTask1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+ freeformTask2);
// Run show desktop apps logic
mController.showDesktopApps();
- ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
- WindowContainerTransaction wct = wctCaptor.getValue();
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
// Check wct has reorder calls
assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 2 has activity later, must be first
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(token2.binder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
- // Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
}
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
+ @Test
+ public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // No reordering needed.
+ assertThat(wct.getHierarchyOps()).isEmpty();
+ }
- WindowContainerToken token() {
- return mToken;
+ @Test
+ public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // Both tasks should be reordered to top, even if one was already visible.
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+ final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
+ }
+
+ @Test
+ public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
+ when(DesktopModeStatus.isActive(any())).thenReturn(false);
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_unsupportedTransit_returnsNull() {
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_notFreeform_returnsNull() {
+ RunningTaskInfo trigger = new RunningTaskInfo();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_taskOpen_returnsWct() {
+ RunningTaskInfo trigger = new RunningTaskInfo();
+ trigger.token = new MockToken().token();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ WindowContainerTransaction wct = mController.handleRequest(
+ mock(IBinder.class),
+ new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
+ assertThat(wct).isNotNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_taskToFront_returnsWct() {
+ RunningTaskInfo trigger = new RunningTaskInfo();
+ trigger.token = new MockToken().token();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ WindowContainerTransaction wct = mController.handleRequest(
+ mock(IBinder.class),
+ new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
+ assertThat(wct).isNotNull();
+ }
+
+ private DesktopModeController createController() {
+ return new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+ }
+
+ private DisplayAreaInfo createMockDisplayArea() {
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
+ return displayAreaInfo;
+ }
+
+ private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+ } else {
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
}
+ return arg.getValue();
+ }
- IBinder binder() {
- return mBinder;
+ private WindowContainerTransaction getBringAppsToFrontTransaction() {
+ final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(arg.capture());
}
+ return arg.getValue();
+ }
+
+ private void assertThatBoundsCleared(Change change) {
+ assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+ assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
}
+
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 9b28d11f6a9d..1e43a5983821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -38,7 +39,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_listenerNotifiedAndTaskIsActive() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
@@ -48,7 +49,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_sameTaskDoesNotNotify() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.addActiveTask(1)
@@ -58,7 +59,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_multipleTasksAddedNotifiesForEach() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.addActiveTask(2)
@@ -68,7 +69,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.removeActiveTask(1)
@@ -80,7 +81,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.removeActiveTask(99)
assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
}
@@ -90,10 +91,95 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(repo.isActiveTask(99)).isFalse()
}
- class TestListener : DesktopModeTaskRepository.Listener {
+ @Test
+ fun addListener_notifiesVisibleFreeformTask() {
+ repo.updateVisibleFreeformTasks(1, true)
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1)
+ }
+
+ @Test
+ fun updateVisibleFreeformTasks_addVisibleTasksNotifiesListener() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.updateVisibleFreeformTasks(1, true)
+ repo.updateVisibleFreeformTasks(2, true)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ // Equal to 2 because adding the listener notifies the current state
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+ }
+
+ @Test
+ fun updateVisibleFreeformTasks_removeVisibleTasksNotifiesListener() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.updateVisibleFreeformTasks(1, true)
+ repo.updateVisibleFreeformTasks(2, true)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ repo.updateVisibleFreeformTasks(1, false)
+ executor.flushAll()
+
+ // Equal to 2 because adding the listener notifies the current state
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+
+ repo.updateVisibleFreeformTasks(2, false)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isFalse()
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks[0]).isEqualTo(7)
+ assertThat(tasks[1]).isEqualTo(6)
+ assertThat(tasks[2]).isEqualTo(5)
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ repo.addOrMoveFreeformTaskToTop(6)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks.first()).isEqualTo(6)
+ }
+
+ class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeTaskChangedCalls = 0
override fun onActiveTasksChanged() {
activeTaskChangedCalls++
}
}
+
+ class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+ var hasVisibleFreeformTasks = false
+ var visibleFreeformTaskChangedCalls = 0
+
+ override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
+ hasVisibleFreeformTasks = hasVisibleTasks
+ visibleFreeformTaskChangedCalls++
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
new file mode 100644
index 000000000000..9a92879bde1f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopTasksControllerTest : ShellTestCase() {
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellController: ShellController
+ @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var transitions: Transitions
+
+ lateinit var mockitoSession: StaticMockitoSession
+ lateinit var controller: DesktopTasksController
+ lateinit var shellInit: ShellInit
+ lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+
+ // Mock running tasks are registered here so we can get the list from mock shell task organizer
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+
+ @Before
+ fun setUp() {
+ mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+ whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeTaskRepository = DesktopModeTaskRepository()
+
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+
+ controller = createController()
+
+ shellInit.init()
+ }
+
+ private fun createController(): DesktopTasksController {
+ return DesktopTasksController(
+ context,
+ shellInit,
+ shellController,
+ shellTaskOrganizer,
+ transitions,
+ desktopModeTaskRepository,
+ TestShellExecutor()
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+
+ runningTasks.clear()
+ }
+
+ @Test
+ fun instantiate_addInitCallback() {
+ verify(shellInit).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ fun instantiate_flagOff_doNotAddInitCallback() {
+ whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+ clearInvocations(shellInit)
+
+ createController()
+
+ verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ fun showDesktopApps_allAppsInvisible_bringsToFront() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ fun showDesktopApps_appsAlreadyVisible_doesNothing() {
+ setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps()
+
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun showDesktopApps_someAppsInvisible_reordersAll() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
+ val homeTask = setUpHomeTask()
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, homeTask)
+ }
+
+ @Test
+ fun moveToDesktop() {
+ val task = setUpFullscreenTask()
+ controller.moveToDesktop(task)
+ val wct = getLatestWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveToDesktop_nonExistentTask_doesNothing() {
+ controller.moveToDesktop(999)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToFullscreen() {
+ val task = setUpFreeformTask()
+ controller.moveToFullscreen(task)
+ val wct = getLatestWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun moveToFullscreen_nonExistentTask_doesNothing() {
+ controller.moveToFullscreen(999)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun getTaskWindowingMode() {
+ val fullscreenTask = setUpFullscreenTask()
+ val freeformTask = setUpFreeformTask()
+
+ assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+ assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_freeformVisible_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(freeformTask2))).isNull()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskHidden(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
+ )
+ assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun handleRequest_notOpenOrToFrontTransition_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build()
+ val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val result = controller.handleRequest(Binder(), transition)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun handleRequest_noTriggerTask_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotStandard_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ private fun setUpFreeformTask(): RunningTaskInfo {
+ val task = createFreeformTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ desktopModeTaskRepository.addActiveTask(task.taskId)
+ desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpHomeTask(): RunningTaskInfo {
+ val task = createHomeTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpFullscreenTask(): RunningTaskInfo {
+ val task = createFullscreenTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun markTaskVisible(task: RunningTaskInfo) {
+ desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
+ }
+
+ private fun markTaskHidden(task: RunningTaskInfo) {
+ desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
+ }
+
+ private fun getLatestWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ }
+ return arg.value
+ }
+
+ private fun verifyWCTNotExecuted() {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(transitions, never()).startTransition(anyInt(), any(), isNull())
+ } else {
+ verify(shellTaskOrganizer, never()).applyTransaction(any())
+ }
+ }
+
+ private fun createTransition(
+ task: RunningTaskInfo?,
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN
+ ): TransitionRequestInfo {
+ return TransitionRequestInfo(type, task, null /* remoteTransition */)
+ }
+}
+
+private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
+ assertWithMessage("WCT does not have a hierarchy operation at index $index")
+ .that(hierarchyOps.size)
+ .isGreaterThan(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ assertThat(op.container).isEqualTo(task.token.asBinder())
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
new file mode 100644
index 000000000000..dc91d756842e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+
+class DesktopTestHelpers {
+ companion object {
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
+ @JvmStatic
+ fun createFreeformTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
+ @JvmStatic
+ fun createFullscreenTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a new home task */
+ @JvmStatic
+ fun createHomeTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
new file mode 100644
index 000000000000..09d474d1f97c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.window.WindowContainerToken;
+
+/**
+ * {@link WindowContainerToken} wrapper that supports a mock binder
+ */
+class MockToken {
+ private final WindowContainerToken mToken;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ IBinder binder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(binder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
deleted file mode 100644
index a88c83779f25..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ /dev/null
@@ -1,248 +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.floating;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/**
- * Tests for the floating tasks controller.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FloatingTasksControllerTest extends ShellTestCase {
- // Some behavior in the controller constructor is dependent on this so we can only
- // validate if it's working for the real value for those things.
- private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-
- @Mock private ShellInit mShellInit;
- @Mock private ShellController mShellController;
- @Mock private WindowManager mWindowManager;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor;
-
- private FloatingTasksController mController;
-
- @Before
- public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
- WindowMetrics windowMetrics = mock(WindowMetrics.class);
- WindowInsets windowInsets = mock(WindowInsets.class);
- Insets insets = Insets.of(0, 0, 0, 0);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
- when(windowMetrics.getWindowInsets()).thenReturn(windowInsets);
- when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000));
- when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets);
-
- // For the purposes of this test, just run everything synchronously
- ShellExecutor shellExecutor = new TestShellExecutor();
- when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor);
- }
-
- @After
- public void tearDown() {
- if (mController != null) {
- mController.removeTask();
- mController = null;
- }
- }
-
- private void setUpTabletConfig() {
- Configuration config = mock(Configuration.class);
- config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
- mController.setConfig(config);
- }
-
- private void setUpPhoneConfig() {
- Configuration config = mock(Configuration.class);
- config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1;
- mController.setConfig(config);
- }
-
- private void createController() {
- mController = new FloatingTasksController(mContext,
- mShellInit,
- mShellController,
- mock(ShellCommandHandler.class),
- Optional.empty(),
- mWindowManager,
- mTaskOrganizer,
- mock(TaskViewTransitions.class),
- mock(ShellExecutor.class),
- mock(ShellExecutor.class),
- mock(SyncTransactionQueue.class));
- spyOn(mController);
- }
-
- //
- // Shell specific
- //
- @Test
- public void instantiateController_addInitCallback() {
- if (FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
- setUpTabletConfig();
-
- verify(mShellInit, times(1)).addInitCallback(any(), any());
- }
- }
-
- @Test
- public void instantiateController_doesntAddInitCallback() {
- if (!FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
-
- verify(mShellInit, never()).addInitCallback(any(), any());
- }
- }
-
- @Test
- public void onInit_registerConfigChangeListener() {
- if (FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
- setUpTabletConfig();
- mController.onInit();
-
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
- }
-
- //
- // Tests for floating layer, which is only available for tablets.
- //
-
- @Test
- public void testIsFloatingLayerAvailable_true() {
- createController();
- setUpTabletConfig();
- assertThat(mController.isFloatingLayerAvailable()).isTrue();
- }
-
- @Test
- public void testIsFloatingLayerAvailable_false() {
- createController();
- setUpPhoneConfig();
- assertThat(mController.isFloatingLayerAvailable()).isFalse();
- }
-
- //
- // Tests for floating tasks being enabled, guarded by sysprop flag.
- //
-
- @Test
- public void testIsFloatingTasksEnabled_true() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
- assertThat(mController.isFloatingTasksEnabled()).isTrue();
- }
-
- @Test
- public void testIsFloatingTasksEnabled_false() {
- createController();
- mController.setFloatingTasksEnabled(false);
- setUpTabletConfig();
- assertThat(mController.isFloatingTasksEnabled()).isFalse();
- }
-
- //
- // Tests for behavior depending on flags
- //
-
- @Test
- public void testShowTaskIntent_enabled() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
- }
-
- @Test
- public void testShowTaskIntent_notEnabled() {
- createController();
- mController.setFloatingTasksEnabled(false);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager, never()).addView(any(), any());
- }
-
- @Test
- public void testRemoveTask() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-
- mController.removeTask();
- verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0);
- }
-}
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 0fd5cb081ea9..48415d47304c 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
@@ -17,17 +17,10 @@
package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
-
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
@@ -44,9 +37,9 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -65,9 +58,7 @@ public class FreeformTaskTransitionObserverTest {
@Mock
private Transitions mTransitions;
@Mock
- private FullscreenTaskListener<?> mFullscreenTaskListener;
- @Mock
- private FreeformTaskListener<?> mFreeformTaskListener;
+ private WindowDecorViewModel mWindowDecorViewModel;
private FreeformTaskTransitionObserver mTransitionObserver;
@@ -82,7 +73,7 @@ public class FreeformTaskTransitionObserverTest {
doReturn(pm).when(context).getPackageManager();
mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener);
+ context, mShellInit, mTransitions, mWindowDecorViewModel);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
Runnable.class);
@@ -112,11 +103,12 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT);
+ verify(mWindowDecorViewModel).onTaskOpening(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
- public void testObtainsWindowDecorOnCloseTransition_freeform() {
+ public void testPreparesWindowDecorOnCloseTransition_freeform() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
@@ -128,7 +120,8 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+ verify(mWindowDecorViewModel).onTaskClosing(
+ change.getTaskInfo(), startT, finishT);
}
@Test
@@ -138,17 +131,13 @@ public class FreeformTaskTransitionObserverTest {
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
info.addChange(change);
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(windowDecor, never()).close();
+ verify(mWindowDecorViewModel, never()).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -159,8 +148,6 @@ public class FreeformTaskTransitionObserverTest {
info.addChange(change);
final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -169,7 +156,7 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionStarting(transition);
mTransitionObserver.onTransitionFinished(transition, false);
- verify(windowDecor).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -192,10 +179,6 @@ public class FreeformTaskTransitionObserverTest {
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -204,7 +187,7 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor2).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
@Test
@@ -215,10 +198,6 @@ public class FreeformTaskTransitionObserverTest {
final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
info1.addChange(change1);
- final AutoCloseable windowDecor1 = mock(AutoCloseable.class);
- doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change1.getTaskInfo()), any(), any());
-
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
@@ -231,10 +210,6 @@ public class FreeformTaskTransitionObserverTest {
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -243,48 +218,8 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor1).close();
- verify(windowDecor2).close();
- }
-
- @Test
- public void testTransfersWindowDecorOnMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN);
- final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0);
- info.addChange(change);
-
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFullscreenTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
- }
-
- @Test
- public void testTransfersWindowDecorOnRestoreFromMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0);
- info.addChange(change);
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFreeformTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change1.getTaskInfo());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index cf8297eec061..8ad3d2a72617 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +177,12 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testControllerRegisteresExternalInterface() {
+ verify(mMockShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
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 1e08f1e55797..35c09a121a1c 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
@@ -18,8 +18,11 @@ package com.android.wm.shell.pip.phone;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -29,11 +32,15 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.lang.Integer.MAX_VALUE;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -58,9 +65,11 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -133,12 +142,12 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipController));
}
@Test
public void instantiateController_registerDumpCallback() {
- verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), eq(mPipController));
}
@Test
@@ -152,6 +161,12 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
+ public void instantiatePipController_registerExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
+ }
+
+ @Test
public void instantiatePipController_registerUserChangeListener() {
verify(mShellController, times(1)).addUserChangeListener(any());
}
@@ -177,6 +192,24 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
+ public void testInvalidateExternalInterface_unregistersListener() {
+ mPipController.setPinnedStackAnimationListener(new PipController.PipAnimationListener() {
+ @Override
+ public void onPipAnimationStarted() {}
+ @Override
+ public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {}
+ @Override
+ public void onExpandPip() {}
+ });
+ assertTrue(mPipController.hasPinnedStackAnimationListener());
+ // Create initial interface
+ mShellController.createExternalInterfaces(new Bundle());
+ // Recreate the interface to trigger invalidation of the previous instance
+ mShellController.createExternalInterfaces(new Bundle());
+ assertFalse(mPipController.hasPinnedStackAnimationListener());
+ }
+
+ @Test
public void createPip_notSupported_returnsNull() {
Context spyContext = spy(mContext);
PackageManager mockPackageManager = mock(PackageManager.class);
@@ -244,6 +277,10 @@ 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.getBounds()).thenReturn(bounds);
+ 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(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
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 dba037db72eb..3bd2ae76ebfd 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.phone;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipResizeGestureHandlerTest extends ShellTestCase {
+ private static final float DEFAULT_SNAP_FRACTION = 2.0f;
private static final int STEP_SIZE = 40;
private final MotionEvent.PointerProperties[] mPp = new MotionEvent.PointerProperties[2];
@@ -196,6 +198,51 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
< mPipBoundsState.getBounds().width());
}
+ @Test
+ public void testUserResizeTo() {
+ // resizing the bounds to normal bounds at first
+ mPipResizeGestureHandler.userResizeTo(mPipBoundsState.getNormalBounds(),
+ DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(mPipBoundsState.getNormalBounds());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(1))
+ .scheduleFinishResizePip(any(), any());
+
+ // bounds with max size
+ final Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+
+ // resizing the bounds to maximum bounds the second time
+ mPipResizeGestureHandler.userResizeTo(maxBounds, DEFAULT_SNAP_FRACTION);
+
+ assertPipBoundsUserResizedTo(maxBounds);
+
+ // another call to scheduleUserResizePip() and scheduleFinishResizePip() makes
+ // the total number of invocations 2 for each method
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleUserResizePip(any(), any(), any());
+
+ verify(mPipTaskOrganizer, times(2))
+ .scheduleFinishResizePip(any(), any());
+ }
+
+ private void assertPipBoundsUserResizedTo(Rect bounds) {
+ // check user-resized bounds
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().width(), bounds.width());
+ assertEquals(mPipResizeGestureHandler.getUserResizeBounds().height(), bounds.height());
+
+ // check if the bounds are the same
+ assertEquals(mPipBoundsState.getBounds().width(), bounds.width());
+ assertEquals(mPipBoundsState.getBounds().height(), bounds.height());
+
+ // a flag should be set to indicate pip has been resized by the user
+ assertTrue(mPipBoundsState.hasUserResizedPip());
+ }
+
private MotionEvent obtainMotionEvent(int action, int topLeft, int bottomRight) {
final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[2];
for (int i = 0; i < 2; i++) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa76e3c7..82392ad9a3eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -23,11 +23,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
+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;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -44,6 +46,7 @@ import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
+import android.os.Bundle;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
@@ -57,7 +60,9 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -92,7 +97,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
+ private RecentTasksController mRecentTasksControllerReal;
private ShellInit mShellInit;
+ private ShellController mShellController;
private TestShellExecutor mMainExecutor;
@Before
@@ -100,9 +107,12 @@ public class RecentTasksControllerTest extends ShellTestCase {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
- mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mMainExecutor));
+ mShellController = spy(new ShellController(mShellInit, mShellCommandHandler,
+ mMainExecutor));
+ mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mMainExecutor);
+ mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
mMainExecutor);
@@ -121,6 +131,26 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ }
+
+ @Test
+ public void testInvalidateExternalInterface_unregistersListener() {
+ // Note: We have to use the real instance of the controller here since that is the instance
+ // that is passed to ShellController internally, and the instance that the listener will be
+ // unregistered from
+ mRecentTasksControllerReal.registerRecentTasksListener(new IRecentTasksListener.Default());
+ assertTrue(mRecentTasksControllerReal.hasRecentTasksListener());
+ // Create initial interface
+ mShellController.createExternalInterfaces(new Bundle());
+ // Recreate the interface to trigger invalidation of the previous instance
+ mShellController.createExternalInterfaces(new Bundle());
+ assertFalse(mRecentTasksControllerReal.hasRecentTasksListener());
+ }
+
+ @Test
public void testAddRemoveSplitNotifyChange() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5a68361c595c..ea3af9d96aa4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,27 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,10 +44,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -58,16 +71,17 @@ import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -76,7 +90,6 @@ import java.util.Optional;
public class SplitScreenControllerTests extends ShellTestCase {
@Mock ShellInit mShellInit;
- @Mock ShellController mShellController;
@Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@@ -89,26 +102,33 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
+ private ShellController mShellController;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
+ mShellController = spy(new ShellController(mShellInit, mShellCommandHandler,
+ mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
public void instantiateController_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit, times(1)).addInitCallback(any(), isA(SplitScreenController.class));
}
@Test
+ @UiThreadTest
public void instantiateController_registerDumpCallback() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -117,6 +137,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void instantiateController_registerCommandCallback() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -125,6 +146,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testControllerRegistersKeyguardChangeListener() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -133,58 +155,123 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
+ @UiThreadTest
+ public void instantiateController_addExternalInterface() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ }
+
+ @Test
+ public void testInvalidateExternalInterface_unregistersListener() {
+ mSplitScreenController.onInit();
+ mSplitScreenController.registerSplitScreenListener(
+ new SplitScreen.SplitScreenListener() {});
+ verify(mStageCoordinator).registerSplitScreenListener(any());
+ // Create initial interface
+ mShellController.createExternalInterfaces(new Bundle());
+ // Recreate the interface to trigger invalidation of the previous instance
+ mShellController.createExternalInterfaces(new Bundle());
+ verify(mStageCoordinator).unregisterSplitScreenListener(any());
+ }
+
+ @Test
+ public void testStartIntent_appendsNoUserActionFlag() {
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
- // Verify launching the same activity returns true.
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
-
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
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 ea0033ba4bbb..652f9b38c88f 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
@@ -181,7 +181,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null);
+ new RemoteTransition(testRemote), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -421,7 +421,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 835087007b30..65e1ea881b26 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
@@ -50,6 +50,7 @@ import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -113,6 +114,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private StageCoordinator mStageCoordinator;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
@@ -153,7 +155,7 @@ public class StageCoordinatorTests extends ShellTestCase {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
// Verify move to undefined stage while split screen not activated moves task to side stage.
- when(mMainStage.isActive()).thenReturn(false);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
new WindowContainerTransaction());
@@ -161,7 +163,7 @@ public class StageCoordinatorTests extends ShellTestCase {
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
// Verify move to undefined stage after split screen activated moves task based on position.
- when(mMainStage.isActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
new WindowContainerTransaction());
@@ -260,7 +262,7 @@ public class StageCoordinatorTests extends ShellTestCase {
@Test
public void testResolveStartStage_afterSplitActivated_retrievesStagePosition() {
- when(mMainStage.isActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
@@ -318,4 +320,16 @@ public class StageCoordinatorTests extends ShellTestCase {
assertTrue(options.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
}
+
+ @Test
+ public void testExitSplitScreenAfterFolded() {
+ when(mMainStage.isActive()).thenReturn(true);
+ when(mMainStage.isFocused()).thenReturn(true);
+ when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+
+ mStageCoordinator.onFoldedStateChanged(true);
+
+ verify(mStageCoordinator).onSplitScreenExit();
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3bb6e8..10dec9ef12f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -16,27 +16,36 @@
package com.android.wm.shell.startingsurface;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.os.Bundle;
import android.view.Display;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -56,25 +65,51 @@ public class StartingWindowControllerTests extends ShellTestCase {
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
- private @Mock ShellInit mShellInit;
+ private @Mock ShellCommandHandler mShellCommandHandler;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
private @Mock IconProvider mIconProvider;
private @Mock TransactionPool mTransactionPool;
private StartingWindowController mController;
+ private ShellInit mShellInit;
+ private ShellController mShellController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
- mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
- mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mShellController = spy(new ShellController(mShellInit, mShellCommandHandler,
+ mMainExecutor));
+ mController = new StartingWindowController(mContext, mShellInit, mShellController,
+ mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit.init();
}
@Test
- public void instantiate_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), isA(StartingWindowController.class));
+ }
+
+ @Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ }
+
+ @Test
+ public void testInvalidateExternalInterface_unregistersListener() {
+ mController.setStartingWindowListener(new TriConsumer<Integer, Integer, Integer>() {
+ @Override
+ public void accept(Integer integer, Integer integer2, Integer integer3) {}
+ });
+ assertTrue(mController.hasStartingWindowListener());
+ // Create initial interface
+ mShellController.createExternalInterfaces(new Bundle());
+ // Recreate the interface to trigger invalidation of the previous instance
+ mShellController.createExternalInterfaces(new Bundle());
+ assertFalse(mController.hasStartingWindowListener());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9e927d..8d92d0864338 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.sysui;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +34,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
@@ -49,16 +55,16 @@ import java.util.Locale;
public class ShellControllerTest extends ShellTestCase {
private static final int TEST_USER_ID = 100;
+ private static final String EXTRA_TEST_BINDER = "test_binder";
@Mock
private ShellInit mShellInit;
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private ShellExecutor mExecutor;
- @Mock
private Context mTestUserContext;
+ private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
@@ -71,6 +77,7 @@ public class ShellControllerTest extends ShellTestCase {
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mExecutor = new TestShellExecutor();
mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -81,6 +88,48 @@ public class ShellControllerTest extends ShellTestCase {
}
@Test
+ public void testAddExternalInterface_ensureCallback() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+ Bundle b = new Bundle();
+ mController.asShell().createExternalInterfaces(b);
+ mExecutor.flushAll();
+ assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+ }
+
+ @Test
+ public void testAddExternalInterface_disallowDuplicateKeys() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ assertThrows(IllegalArgumentException.class, () -> {
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ });
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
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 c6492bee040e..595c3b4880df 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,7 +45,6 @@ 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;
@@ -67,10 +66,12 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.IWindowContainerToken;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
@@ -86,7 +87,9 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -117,18 +120,31 @@ public class ShellTransitionTests extends ShellTestCase {
@Before
public void setUp() {
doAnswer(invocation -> invocation.getArguments()[1])
- .when(mOrganizer).startTransition(anyInt(), any(), any());
+ .when(mOrganizer).startTransition(any(), any());
}
@Test
public void instantiate_addInitCallback() {
ShellInit shellInit = mock(ShellInit.class);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
verify(shellInit, times(1)).addInitCallback(any(), eq(t));
}
@Test
+ public void instantiateController_addExternalInterface() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ ShellController shellController = mock(ShellController.class);
+ final Transitions t = new Transitions(mContext, shellInit, shellController,
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ verify(shellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -136,7 +152,7 @@ public class ShellTransitionTests extends ShellTestCase {
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -188,7 +204,7 @@ public class ShellTransitionTests extends ShellTestCase {
// Make a request that will be rejected by the testhandler.
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -199,10 +215,12 @@ public class ShellTransitionTests extends ShellTestCase {
// Make a request that will be handled by testhandler but not animated by it.
RunningTaskInfo mwTaskInfo =
createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ // Make the wct non-empty.
+ handlerWCT.setFocusable(new WindowContainerToken(mock(IWindowContainerToken.class)), true);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+ eq(transitToken), eq(handlerWCT));
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -217,8 +235,8 @@ public class ShellTransitionTests extends ShellTestCase {
transitions.addHandler(topHandler);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT));
+ verify(mOrganizer, times(2)).startTransition(
+ eq(transitToken), eq(handlerWCT));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
@@ -256,7 +274,7 @@ public class ShellTransitionTests extends ShellTestCase {
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
new RemoteTransition(testRemote)));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -406,7 +424,7 @@ public class ShellTransitionTests extends ShellTestCase {
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -918,7 +936,7 @@ public class ShellTransitionTests extends ShellTestCase {
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
RunningTaskInfo taskInfo) {
final TransitionInfo.Change change =
- new TransitionInfo.Change(null /* token */, null /* leash */);
+ new TransitionInfo.Change(null /* token */, createMockSurface(true));
change.setMode(mode);
change.setTaskInfo(taskInfo);
mInfo.addChange(change);
@@ -943,7 +961,7 @@ public class ShellTransitionTests extends ShellTestCase {
final TransitionInfo.Change mChange;
ChangeBuilder(@WindowManager.TransitionType int mode) {
- mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+ mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
mChange.setMode(mode);
}
@@ -1060,8 +1078,9 @@ public class ShellTransitionTests extends ShellTestCase {
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
index 81eefe25704e..8196c5ab08e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -224,16 +224,18 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+ mUnfoldAnimationController.onStateChangeStarted();
mUnfoldAnimationController.onTaskVanished(taskInfo);
+ mUnfoldAnimationController.onStateChangeFinished();
assertThat(mTaskAnimator1.mResetTasks).contains(taskInfo.taskId);
}
@Test
- public void testApplicablePinnedTaskDisappeared_doesNotResetSurface() {
- mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ public void testApplicableTaskDisappeared_noStateChange_doesNotResetSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 0);
RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
- .setWindowingMode(2).build();
+ .setWindowingMode(0).build();
mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
@@ -249,7 +251,9 @@ public class UnfoldAnimationControllerTest extends ShellTestCase {
.setWindowingMode(0).build();
mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ mUnfoldAnimationController.onStateChangeStarted();
mUnfoldAnimationController.onTaskVanished(taskInfo);
+ mUnfoldAnimationController.onStateChangeFinished();
assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
new file mode 100644
index 000000000000..355072116cb1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Tests of {@link DesktopModeWindowDecorViewModel} */
+@SmallTest
+public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
+
+ private static final String TAG = "DesktopModeWindowDecorViewModelTests";
+
+ @Mock private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
+ @Mock private DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
+
+ @Mock private Handler mMainHandler;
+ @Mock private Choreographer mMainChoreographer;
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private DisplayController mDisplayController;
+ @Mock private SyncTransactionQueue mSyncQueue;
+ @Mock private DesktopModeController mDesktopModeController;
+ @Mock private DesktopTasksController mDesktopTasksController;
+ @Mock private InputMonitor mInputMonitor;
+ @Mock private InputManager mInputManager;
+
+ @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
+ private final List<InputManager> mMockInputManagers = new ArrayList<>();
+
+ private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
+
+ @Before
+ public void setUp() {
+ mMockInputManagers.add(mInputManager);
+
+ mDesktopModeWindowDecorViewModel =
+ new DesktopModeWindowDecorViewModel(
+ mContext,
+ mMainHandler,
+ mMainChoreographer,
+ mTaskOrganizer,
+ mDisplayController,
+ mSyncQueue,
+ Optional.of(mDesktopModeController),
+ Optional.of(mDesktopTasksController),
+ mDesktopModeWindowDecorFactory,
+ mMockInputMonitorFactory
+ );
+
+ doReturn(mDesktopModeWindowDecoration)
+ .when(mDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), any(), any(), any(), any(), any());
+
+ when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+ inputChannels[0].dispose();
+ when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+ }
+
+ @Test
+ public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mDesktopModeWindowDecorViewModel.onTaskOpening(
+ taskInfo, surfaceControl, startT, finishT);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mDesktopModeWindowDecorViewModel.onTaskChanging(
+ taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mDesktopModeWindowDecorFactory)
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ verify(mDesktopModeWindowDecoration).close();
+ }
+
+ @Test
+ public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+
+ mDesktopModeWindowDecorViewModel.onTaskChanging(
+ taskInfo, surfaceControl, startT, finishT);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+ mDesktopModeWindowDecorViewModel.onTaskChanging(
+ taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mDesktopModeWindowDecorFactory, times(1))
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ }
+
+ @Test
+ public void testCreateAndDisposeEventReceiver() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ runOnMainThread(() -> {
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mDesktopModeWindowDecorViewModel.onTaskOpening(
+ taskInfo, surfaceControl, startT, finishT);
+
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory).create(any(), any());
+ verify(mInputMonitor).dispose();
+ }
+
+ @Test
+ public void testEventReceiversOnMultipleDisplays() throws Exception {
+ runOnMainThread(() -> {
+ SurfaceView surfaceView = new SurfaceView(mContext);
+ final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+ final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+ /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ try {
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
+ finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ } finally {
+ secondaryDisplay.release();
+ }
+ });
+ verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+ verify(mInputMonitor, times(1)).dispose();
+ }
+
+ private void runOnMainThread(Runnable r) throws Exception {
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ final CountDownLatch latch = new CountDownLatch(1);
+ mainHandler.post(() -> {
+ r.run();
+ latch.countDown();
+ });
+ latch.await(1, TimeUnit.SECONDS);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+ int displayId, int windowingMode) {
+ ActivityManager.RunningTaskInfo taskInfo =
+ new TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setVisible(true)
+ .build();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ return taskInfo;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 000000000000..ac10ddb0116a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ private lateinit var taskPositioner: TaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner = TaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDragStartListener
+ )
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ }
+
+ @Test
+ fun testDragResize_move_skipsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_UNDEFINED, // Move
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Move the task 10px to the right.
+ val newX = STARTING_BOUNDS.left.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize the task by 10px to the right.
+ val newX = STARTING_BOUNDS.right.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ !change.dragResizing
+ }
+ })
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index ab6ac949d4a3..ec4f17fd072b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -47,14 +48,17 @@ import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.tests.R;
import org.junit.Before;
import org.junit.Test;
@@ -62,6 +66,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -76,12 +81,9 @@ import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class WindowDecorationTests extends ShellTestCase {
- private static final int CAPTION_HEIGHT_DP = 32;
- private static final int SHADOW_RADIUS_DP = 5;
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
- private final Rect mOutsetsDp = new Rect();
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -103,11 +105,20 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
+ private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+ mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
+
+ mRelayoutParams.mLayoutResId = 0;
+ mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+ // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
+ mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
@@ -146,7 +157,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -196,8 +211,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
-
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -215,20 +233,22 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT)
.setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
- verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
+ verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface,
+ TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
+
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
- && lp.width == 300
+ && lp.width == 432
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
@@ -247,7 +267,6 @@ public class WindowDecorationTests extends ShellTestCase {
assertEquals(380, mRelayoutResult.mWidth);
assertEquals(220, mRelayoutResult.mHeight);
- assertEquals(2, mRelayoutResult.mDensity, 0.f);
}
@Test
@@ -286,7 +305,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -355,9 +378,127 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
+ @Test
+ public void testAddWindow() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ windowDecor.relayout(taskInfo);
+
+ final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+ createMockSurfaceControlBuilder(additionalWindowSurface);
+ mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+ WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+ verify(additionalWindowSurfaceBuilder).setContainerLayer();
+ verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+ verify(additionalWindowSurfaceBuilder).build();
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+ verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+ .create(any(), eq(defaultDisplay), any());
+ assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+ additionalWindow.releaseView();
+
+ assertThat(additionalWindow.mWindowViewHost).isNull();
+ assertThat(additionalWindow.mWindowSurface).isNull();
+ }
+
+ @Test
+ public void testLayoutResultCalculation_fullWidthCaption() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
+ windowDecor.relayout(taskInfo);
+
+ verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
+ verify(captionContainerSurfaceBuilder).setContainerLayer();
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ // Width of the captionContainerSurface should match the width of TASK_BOUNDS
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).show(captionContainerSurface);
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
- return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
+ return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
+ mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, testSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
@@ -409,9 +550,24 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
- mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
+ relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
+ mMockWindowContainerTransaction, mMockView, mRelayoutResult);
+ }
+
+ private WindowDecoration.AdditionalWindow addTestWindow() {
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String name = "Test Window";
+ WindowDecoration.AdditionalWindow additionalWindow =
+ addWindow(R.layout.desktop_mode_decor_handle_menu, name,
+ mMockSurfaceControlAddWindowT,
+ x - mRelayoutResult.mDecorContainerOffsetX,
+ y - mRelayoutResult.mDecorContainerOffsetY,
+ width, height);
+ return additionalWindow;
}
}
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 404d00ac012a..979a660d497f 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -95,6 +95,7 @@ cc_defaults {
name: "hwui_static_deps",
defaults: [
"android.hardware.graphics.common-ndk_shared",
+ "android.hardware.graphics.composer3-ndk_shared",
],
shared_libs: [
"libbase",
@@ -110,7 +111,6 @@ cc_defaults {
android: {
shared_libs: [
"android.hardware.graphics.common@1.2",
- "android.hardware.graphics.composer3-V1-ndk",
"liblog",
"libcutils",
"libutils",