summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java8
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java20
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java345
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java41
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java110
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java98
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java9
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java8
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java490
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java108
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java52
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java26
-rw-r--r--libs/WindowManager/Shell/Android.bp3
-rw-r--r--libs/WindowManager/Shell/aconfig/Android.bp12
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig30
-rw-r--r--libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto2
-rw-r--r--libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml28
-rw-r--r--libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml (renamed from libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml)5
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml (renamed from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml14
-rw-r--r--libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml26
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml56
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml9
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml22
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml148
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml57
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml47
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml62
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml54
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml32
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings_tv.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml23
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-television/config.xml13
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-watch/colors.xml22
-rw-r--r--libs/WindowManager/Shell/res/values-watch/config.xml35
-rw-r--r--libs/WindowManager/Shell/res/values-watch/dimen.xml22
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml16
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml7
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml41
-rw-r--r--libs/WindowManager/Shell/res/values/ids.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java461
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java145
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java92
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt206
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt)17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java557
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt149
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java179
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java133
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java)62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt)21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java115
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt)23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java286
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java307
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java316
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt245
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt179
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp41
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml (renamed from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml)43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml (renamed from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml)14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt)7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt)32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt)33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt190
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt)31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/Android.bp34
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/OWNERS (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml (renamed from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml)14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt)15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt)7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt)8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/Android.bp136
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml80
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/OWNERS (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml121
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml (renamed from libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml)14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt)26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt)23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt)13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt)11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt)1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt)1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt)19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt)1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt)90
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt)20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt)5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt234
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt142
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt149
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt91
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt)8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/Android.bp67
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt)10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt)14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt)7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt)10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt)7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt)4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt)6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt)16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt)0
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt159
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt)20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt)1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt)3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt)14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt)12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt)10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt)10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt)10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt)19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt)1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto75
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java202
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java)7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt201
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java234
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt50
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt59
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java531
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt114
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java249
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java319
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java56
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java99
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java174
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java73
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java)2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java95
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java318
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt426
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java133
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt101
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java234
-rw-r--r--libs/androidfw/Android.bp1
-rw-r--r--libs/androidfw/ApkParsing.cpp5
-rw-r--r--libs/androidfw/AssetManager2.cpp397
-rw-r--r--libs/androidfw/Idmap.cpp10
-rw-r--r--libs/androidfw/LoadedArsc.cpp21
-rw-r--r--libs/androidfw/ResourceTypes.cpp108
-rw-r--r--libs/androidfw/ZipFileRO.cpp68
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h36
-rw-r--r--libs/androidfw/include/androidfw/Errors.h2
-rw-r--r--libs/androidfw/include/androidfw/IDiagnostics.h19
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h3
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h5
-rw-r--r--libs/androidfw/include/androidfw/ZipFileRO.h13
-rw-r--r--libs/androidfw/tests/ApkParsing_test.cpp6
-rw-r--r--libs/androidfw/tests/AssetManager2_bench.cpp6
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp20
-rw-r--r--libs/androidfw/tests/BenchmarkHelpers.cpp2
-rw-r--r--libs/androidfw/tests/Generic_bench.cpp146
-rw-r--r--libs/androidfw/tests/Theme_test.cpp2
-rw-r--r--libs/hwui/Android.bp21
-rw-r--r--libs/hwui/AutoBackendTextureRelease.cpp59
-rw-r--r--libs/hwui/ColorFilter.h94
-rw-r--r--libs/hwui/DisplayList.h2
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/FeatureFlags.h48
-rw-r--r--libs/hwui/FrameInfo.cpp26
-rw-r--r--libs/hwui/FrameInfo.h14
-rw-r--r--libs/hwui/FrameInfoVisualizer.cpp4
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp7
-rw-r--r--libs/hwui/MemoryPolicy.cpp3
-rw-r--r--libs/hwui/MemoryPolicy.h2
-rw-r--r--libs/hwui/Mesh.h30
-rw-r--r--libs/hwui/Properties.cpp2
-rw-r--r--libs/hwui/Readback.cpp31
-rw-r--r--libs/hwui/RecordingCanvas.cpp35
-rw-r--r--libs/hwui/RecordingCanvas.h4
-rw-r--r--libs/hwui/RenderNode.cpp44
-rw-r--r--libs/hwui/RenderNode.h8
-rw-r--r--libs/hwui/RenderProperties.cpp7
-rw-r--r--libs/hwui/RenderProperties.h5
-rw-r--r--libs/hwui/SkiaCanvas.cpp5
-rw-r--r--libs/hwui/SkiaCanvas.h2
-rw-r--r--libs/hwui/SkiaInterpolator.cpp71
-rw-r--r--libs/hwui/SkiaInterpolator.h10
-rw-r--r--libs/hwui/SkiaWrapper.h56
-rw-r--r--libs/hwui/SkippedFrameInfo.h (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt)23
-rw-r--r--libs/hwui/Tonemapper.cpp12
-rw-r--r--libs/hwui/TreeInfo.h18
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig43
-rw-r--r--libs/hwui/apex/android_bitmap.cpp2
-rw-r--r--libs/hwui/apex/android_canvas.cpp4
-rw-r--r--libs/hwui/apex/jni_runtime.cpp3
-rw-r--r--libs/hwui/effects/GainmapRenderer.cpp17
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp4
-rw-r--r--libs/hwui/hwui/Bitmap.cpp41
-rw-r--r--libs/hwui/hwui/Canvas.cpp112
-rw-r--r--libs/hwui/hwui/Canvas.h2
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h141
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp3
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp24
-rw-r--r--libs/hwui/hwui/MinikinUtils.h9
-rw-r--r--libs/hwui/hwui/Paint.h5
-rw-r--r--libs/hwui/hwui/Typeface.cpp5
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp16
-rw-r--r--libs/hwui/jni/Bitmap.cpp2
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp3
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp3
-rw-r--r--libs/hwui/jni/ColorFilter.cpp75
-rw-r--r--libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp28
-rw-r--r--libs/hwui/jni/FontFamily.cpp6
-rw-r--r--libs/hwui/jni/Gainmap.cpp3
-rw-r--r--libs/hwui/jni/Graphics.cpp3
-rw-r--r--libs/hwui/jni/GraphicsJNI.h8
-rw-r--r--libs/hwui/jni/GraphicsStatsService.cpp3
-rw-r--r--libs/hwui/jni/MaskFilter.cpp12
-rw-r--r--libs/hwui/jni/NinePatch.cpp2
-rw-r--r--libs/hwui/jni/Paint.cpp184
-rw-r--r--libs/hwui/jni/RenderEffect.cpp12
-rw-r--r--libs/hwui/jni/Shader.cpp60
-rw-r--r--libs/hwui/jni/YuvToJpegEncoder.cpp3
-rw-r--r--libs/hwui/jni/android_graphics_DisplayListCanvas.cpp8
-rw-r--r--libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp21
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp9
-rw-r--r--libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp11
-rw-r--r--libs/hwui/jni/fonts/Font.cpp23
-rw-r--r--libs/hwui/jni/fonts/FontFamily.cpp10
-rw-r--r--libs/hwui/jni/pdf/PdfEditor.cpp3
-rw-r--r--libs/hwui/jni/pdf/PdfUtils.cpp7
-rw-r--r--libs/hwui/jni/text/GraphemeBreak.cpp3
-rw-r--r--libs/hwui/jni/text/LineBreaker.cpp83
-rw-r--r--libs/hwui/jni/text/MeasuredText.cpp19
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp51
-rw-r--r--libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp96
-rw-r--r--libs/hwui/pipeline/skia/BackdropFilterDrawable.h67
-rw-r--r--libs/hwui/pipeline/skia/GLFunctorDrawable.cpp25
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp37
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.h7
-rw-r--r--libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp1
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h1
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp6
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp41
-rw-r--r--libs/hwui/pipeline/skia/SkiaMemoryTracer.h15
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp17
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp29
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp20
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.h2
-rw-r--r--libs/hwui/pipeline/skia/StretchMask.cpp23
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp8
-rw-r--r--libs/hwui/private/hwui/DrawGlInfo.h3
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp22
-rw-r--r--libs/hwui/renderthread/CacheManager.h3
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp105
-rw-r--r--libs/hwui/renderthread/CanvasContext.h8
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp7
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp48
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.h11
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp17
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp13
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp47
-rw-r--r--libs/hwui/renderthread/VulkanManager.h5
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp8
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp10
-rw-r--r--libs/hwui/tests/common/TestUtils.h64
-rw-r--r--libs/hwui/tests/common/scenes/ListViewAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp2
-rw-r--r--libs/hwui/tests/macrobench/main.cpp86
-rw-r--r--libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp2
-rw-r--r--libs/hwui/tests/unit/CacheManagerTests.cpp16
-rw-r--r--libs/hwui/tests/unit/CanvasContextTests.cpp36
-rw-r--r--libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp2
-rw-r--r--libs/hwui/tests/unit/HintSessionWrapperTests.cpp193
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp101
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp3
-rw-r--r--libs/hwui/tests/unit/ShaderCacheTests.cpp4
-rw-r--r--libs/hwui/tests/unit/SkiaCanvasTests.cpp2
-rw-r--r--libs/hwui/tests/unit/SkiaDisplayListTests.cpp37
-rw-r--r--libs/hwui/tests/unit/SkiaPipelineTests.cpp36
-rw-r--r--libs/hwui/tests/unit/UnderlineTest.cpp153
-rw-r--r--libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp22
-rw-r--r--libs/hwui/tests/unit/main.cpp64
-rw-r--r--libs/input/Android.bp14
-rw-r--r--libs/input/MouseCursorController.cpp14
-rw-r--r--libs/input/PointerController.cpp58
-rw-r--r--libs/input/PointerController.h12
-rw-r--r--libs/input/PointerControllerContext.cpp8
-rw-r--r--libs/input/PointerControllerContext.h6
-rw-r--r--libs/input/SpriteController.cpp72
-rw-r--r--libs/input/SpriteController.h30
-rw-r--r--libs/input/TouchSpotController.cpp16
-rw-r--r--libs/input/tests/Android.bp15
-rw-r--r--libs/input/tests/PointerController_test.cpp52
652 files changed, 16517 insertions, 6286 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 9da6c10c6d74..a663f9fafb50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -16,11 +16,13 @@
package androidx.window.extensions;
+import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.extensions.area.WindowAreaComponent;
@@ -104,9 +106,13 @@ public class WindowExtensionsImpl implements WindowExtensions {
* {@link WindowExtensions#getWindowLayoutComponent()}.
* @return {@link ActivityEmbeddingComponent} OEM implementation.
*/
- @NonNull
+ @Nullable
public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
if (mSplitController == null) {
+ if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
+ // Disable AE for device that doesn't support multi window.
+ return null;
+ }
synchronized (mLock) {
if (mSplitController == null) {
mSplitController = new SplitController(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 000000000000..ff49cdcab349
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+ // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS =
+ "androidx.window.extensions.OverlayCreateParams";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+ "androidx.window.extensions.OverlayCreateParams.taskId";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+ "androidx.window.extensions.OverlayCreateParams.tag";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+ "androidx.window.extensions.OverlayCreateParams.bounds";
+
+ private final int mTaskId;
+
+ @NonNull
+ private final String mTag;
+
+ @NonNull
+ private final Rect mBounds;
+
+ OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+ mTaskId = taskId;
+ mTag = requireNonNull(tag);
+ mBounds = requireNonNull(bounds);
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ @NonNull
+ String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTaskId;
+ result = 31 * result + mTag.hashCode();
+ result = 31 * result + mBounds.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+ return mTaskId == thatParams.mTaskId
+ && mTag.equals(thatParams.mTag)
+ && mBounds.equals(thatParams.mBounds);
+ }
+
+ @Override
+ public String toString() {
+ return OverlayCreateParams.class.getSimpleName() + ": {"
+ + "taskId=" + mTaskId
+ + ", tag=" + mTag
+ + ", bounds=" + mBounds
+ + "}";
+ }
+
+ /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+ @Nullable
+ static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+ final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+ if (paramsBundle == null) {
+ return null;
+ }
+ final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+ final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+ final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+ KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+ return new OverlayCreateParams(taskId, tag, bounds);
+ }
+}
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 381e9d472f0f..08b7bb89d10c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -25,6 +25,7 @@ import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.extensions.core.util.function.Function;
/**
@@ -186,10 +187,21 @@ class SplitContainer {
return (mSplitRule instanceof SplitPlaceholderRule);
}
- @NonNull
- SplitInfo toSplitInfo() {
- return new SplitInfo(mPrimaryContainer.toActivityStack(),
- mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
+ /**
+ * Returns the SplitInfo representing this container.
+ *
+ * @return the SplitInfo representing this container if the underlying TaskFragmentContainers
+ * are stable, or {@code null} if any TaskFragmentContainer is in an intermediate state.
+ */
+ @Nullable
+ SplitInfo toSplitInfoIfStable() {
+ final ActivityStack primaryActivityStack = mPrimaryContainer.toActivityStackIfStable();
+ final ActivityStack secondaryActivityStack = mSecondaryContainer.toActivityStackIfStable();
+ if (primaryActivityStack == null || secondaryActivityStack == null) {
+ return null;
+ }
+ return new SplitInfo(primaryActivityStack, secondaryActivityStack,
+ mCurrentSplitAttributes, mToken);
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
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 3ad30457697e..76f0b6769855 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -87,6 +88,7 @@ import androidx.window.extensions.embedding.TransactionManager.TransactionRecord
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,8 +125,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
* <ul>
- * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
- * WindowContainerTransaction)}</li>
+ * <li>{@link SplitPresenter#updateSplitContainer}</li>
* <li>There's a started Activity which matches {@link SplitPairRule} </li>
* <li>Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule} </li>
@@ -291,8 +292,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct,
- containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+ false /* isolated */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -759,6 +760,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+ // container.
targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
@@ -877,13 +880,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- // Skip resolving if the activity is on a pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
- if (container != null) {
- final TaskContainer taskContainer = container.getTaskContainer();
- if (taskContainer.isTaskFragmentContainerPinned(container)) {
- return true;
- }
+ // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+ if (container != null && container.isIsolatedNavigationEnabled()) {
+ return true;
}
final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
@@ -891,10 +890,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
// Do not resolve if the launched activity is not the top-most container (excludes
- // the pinned container) in the Task.
+ // the pinned and overlay container) in the Task.
return true;
}
+ // Ensure the top TaskFragments are updated to the right config if activity is resolved
+ // to a new TaskFragment while pin TF exists.
+ final boolean handled = resolveActivityToContainerByRule(wct, activity, container,
+ isOnReparent);
+ if (handled && taskContainer != null) {
+ final SplitPinContainer splitPinContainer = taskContainer.getSplitPinContainer();
+ if (splitPinContainer != null) {
+ final TaskFragmentContainer resolvedContainer = getContainerWithActivity(activity);
+ if (resolvedContainer != null && resolvedContainer.getRunningActivityCount() <= 1) {
+ updateContainer(wct, splitPinContainer.getSecondaryContainer());
+ }
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
+ */
+ boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, @Nullable TaskFragmentContainer container,
+ boolean isOnReparent) {
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new launched activity should always expand.
@@ -987,6 +1008,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer == null) {
return;
}
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
final TaskFragmentContainer targetContainer =
taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
@@ -1146,7 +1168,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- getTaskWindowMetrics(taskProperties.getConfiguration()),
+ taskProperties.getTaskMetrics(),
calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
@@ -1258,9 +1280,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check whether the Intent should be embedded in the known Task.
final TaskContainer taskContainer = mTaskContainers.valueAt(0);
if (taskContainer.isInPictureInPicture()
- || taskContainer.getTopNonFinishingActivity() == null) {
+ || taskContainer.getTopNonFinishingActivity(false /* includeOverlay */) == null) {
// We don't embed activity when it is in PIP, or if we can't find any other owner
- // activity in the Task.
+ // activity in non-overlay container in the Task.
return null;
}
@@ -1288,19 +1310,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
- final TaskContainer taskContainer =
- taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
- if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
- taskFragmentContainer)) {
+ if (taskFragmentContainer != null
+ && taskFragmentContainer.isIsolatedNavigationEnabled()) {
return null;
}
}
+ // Ensure the top TaskFragments are updated to the right config if the intent is resolved
+ // to a new TaskFragment while pin TF exists.
+ final TaskFragmentContainer launchingContainer = resolveStartActivityIntentByRule(wct,
+ taskId, intent, launchingActivity);
+ if (launchingContainer != null && launchingContainer.getRunningActivityCount() == 0) {
+ final SplitPinContainer splitPinContainer =
+ launchingContainer.getTaskContainer().getSplitPinContainer();
+ if (splitPinContainer != null) {
+ updateContainer(wct, splitPinContainer.getSecondaryContainer());
+ }
+ }
+ return launchingContainer;
+ }
+
+ /**
+ * Resolves the intent to a {@link TaskFragmentContainer} according to the Split-rules.
+ */
+ @Nullable
+ TaskFragmentContainer resolveStartActivityIntentByRule(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new activity intent should always expand.
@@ -1368,6 +1407,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
+ return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ null /* overlayTag */);
+ }
+
+ /**
+ * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+ * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+ * overlay container.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createEmptyContainer(
+ @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+ @NonNull Rect bounds, @Nullable Activity launchingActivity,
+ @Nullable String overlayTag) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1376,20 +1431,53 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} else {
final TaskContainer taskContainer = getTaskContainer(taskId);
activityInTask = taskContainer != null
- ? taskContainer.getTopNonFinishingActivity()
+ ? taskContainer.getTopNonFinishingActivity(true /* includeOverlay */)
: null;
}
if (activityInTask == null) {
// Can't find any activity in the Task that we can use as the owner activity.
return null;
}
- final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
- taskId);
- mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
- activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
- mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ final IBinder taskFragmentToken = container.getTaskFragmentToken();
+ // Note that taskContainer will not exist before calling #newContainer if the container
+ // is the first embedded TF in the task.
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final int windowingMode = taskContainer
+ .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+ sanitizedBounds, windowingMode);
+ mPresenter.updateAnimationParams(wct, taskFragmentToken,
TaskFragmentAnimationParams.DEFAULT);
- return expandedContainer;
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+ overlayTag != null && !sanitizedBounds.isEmpty());
+
+ return container;
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
/**
@@ -1409,8 +1497,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
final TaskContainer.TaskProperties taskProperties = mPresenter
.getTaskProperties(primaryActivity);
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
- taskProperties.getConfiguration());
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1479,14 +1566,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ }
+
+ @GuardedBy("mLock")
+ TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
}
/**
@@ -1500,11 +1595,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @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.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1513,7 +1611,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
return container;
}
@@ -1653,31 +1751,6 @@ 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);
- if (taskContainer == null) {
- return null;
- }
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
- for (int i = containers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = containers.get(i);
- if (!container.isFinished() && (container.getRunningActivityCount() > 0
- // We may be waiting for the top TaskFragment to become non-empty after
- // creation. In that case, we don't want to treat the TaskFragment below it as
- // top active, otherwise it may incorrectly launch placeholder on top of the
- // pending TaskFragment.
- || container.isWaitingActivityAppear())) {
- return container;
- }
- }
- return null;
- }
-
- /**
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
@@ -1690,6 +1763,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
+ if (container.isOverlay()) {
+ updateOverlayContainer(wct, container);
+ return;
+ }
+
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1710,17 +1788,35 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
}
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ // Dismiss the overlay container if it's the only container in the task and there's no
+ // direct activity in the parent task.
+ if (taskContainer.getTaskFragmentContainers().size() == 1
+ && !taskContainer.hasDirectActivity()) {
+ container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+ }
+
+ // TODO(b/295805054): Add the logic to update overlay container
+ }
+
/**
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
* are {@code null}, the {@link SplitAttributes} will be calculated with
- * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ * {@link SplitPresenter#computeSplitAttributes}.
*
* @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
- * {@link SplitPresenter#computeSplitAttributes(
- * TaskContainer.TaskProperties, SplitRule, Pair)}
+ * {@link SplitPresenter#computeSplitAttributes}
*
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@@ -1866,7 +1962,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** 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());
+ final TaskFragmentContainer topContainer = container.getTaskContainer()
+ .getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
// The container is not the top most.
if (!container.isVisible()) {
@@ -1972,8 +2069,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- final List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
+ if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1983,13 +2080,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Returns a list of descriptors for currently active split states.
+ *
+ * @return a list of descriptors for currently active split states if all the containers are in
+ * a stable state, or {@code null} otherwise.
*/
@GuardedBy("mLock")
- @NonNull
- private List<SplitInfo> getActiveSplitStates() {
+ @Nullable
+ private List<SplitInfo> getActiveSplitStatesIfStable() {
final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).getSplitStates(splitStates);
+ final List<SplitInfo> taskSplitStates =
+ mTaskContainers.valueAt(i).getSplitStatesIfStable();
+ if (taskSplitStates == null) {
+ return null;
+ }
+ splitStates.addAll(taskSplitStates);
}
return splitStates;
}
@@ -2207,6 +2312,96 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
}
+ /**
+ * Gets all overlay containers from all tasks in this process, or an empty list if there's
+ * no overlay container.
+ * <p>
+ * Note that we only support one overlay container for each task, but an app could have multiple
+ * tasks.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+ final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+ for (int i = 0; i < mTaskContainers.size(); i++) {
+ final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer != null) {
+ overlayContainers.add(overlayContainer);
+ }
+ }
+ return overlayContainers;
+ }
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull Intent intent, @NonNull Activity launchActivity) {
+ final int taskId = overlayCreateParams.getTaskId();
+ if (taskId != launchTaskId) {
+ // The task ID doesn't match the launch activity's. Cannot determine the host task
+ // to launch the overlay.
+ throw new IllegalArgumentException("The task ID of "
+ + "OverlayCreateParams#launchingActivity must match the task ID of "
+ + "the activity to #startActivity with the activity options that takes "
+ + "OverlayCreateParams.");
+ }
+ final List<TaskFragmentContainer> overlayContainers =
+ getAllOverlayTaskFragmentContainers();
+ final String overlayTag = overlayCreateParams.getTag();
+
+ // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+ // specified by Intent, expand the overlay container to fill the parent task instead.
+ final Rect bounds = overlayCreateParams.getBounds();
+ final Size minDimensions = getMinDimensions(intent);
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+ minDimensions);
+ if (!overlayContainers.isEmpty()) {
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ if (!overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with different tag shown in the same
+ // task, dismiss the existing overlay container.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId != overlayContainer.getTaskId()) {
+ // If there's an overlay container with same tag in a different task,
+ // dismiss the overlay container since the tag must be unique per process.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with the same tag and task ID, we treat
+ // the OverlayCreateParams as the update to the container.
+ final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+ .getTaskMetrics().getBounds();
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+ !sanitizedBounds.isEmpty());
+ // We can just return the updated overlay container and don't need to
+ // check other condition since we only have one OverlayCreateParams, and
+ // if the tag and task are matched, it's impossible to match another task
+ // or tag since tags and tasks are all unique.
+ return overlayContainer;
+ }
+ }
+ }
+ return createEmptyContainer(wct, intent, taskId,
+ (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ }
+
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
@@ -2369,8 +2564,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
- launchingActivity);
+ final OverlayCreateParams overlayCreateParams =
+ OverlayCreateParams.fromBundle(options);
+ if (Flags.activityEmbeddingOverlayPresentationFlag()
+ && overlayCreateParams != null) {
+ launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+ overlayCreateParams, taskId, intent, launchingActivity);
+ } else {
+ launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+ launchingActivity);
+ }
} else {
launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
intent);
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 d894487fafb6..faf7c3999402 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
-import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(
- null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(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);
@@ -390,14 +388,27 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
- !isStacked /* isolatedNav */);
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ */
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer taskFragmentContainer,
+ boolean isolatedNavigationEnabled) {
+ if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ return;
+ }
+ taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ isolatedNavigationEnabled);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
@@ -618,7 +629,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
@Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +724,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new Size(windowLayout.minWidth, windowLayout.minHeight);
}
- private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
@Nullable Size minDimensions) {
if (minDimensions == null) {
return false;
}
+ // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+ if (bounds.isEmpty()) {
+ return false;
+ }
return bounds.width() < minDimensions.getWidth()
|| bounds.height() < minDimensions.getHeight();
}
@@ -1066,14 +1081,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
- return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
- }
-
- @NonNull
- static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
- final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
- // TODO(b/190433398): Supply correct insets.
- final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ return getTaskProperties(activity).getTaskMetrics();
}
}
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 463c8ceaf992..028e75fe010f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@ class TaskContainer {
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -68,6 +75,8 @@ class TaskContainer {
private boolean mIsVisible;
+ private boolean mHasDirectActivity;
+
/**
* TaskFragments that the organizer has requested to be closed. They should be removed when
* the organizer receives
@@ -95,8 +104,9 @@ class TaskContainer {
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.
+ // the host task is visible and has an activity in the task.
mIsVisible = true;
+ mHasDirectActivity = true;
}
int getTaskId() {
@@ -111,6 +121,10 @@ class TaskContainer {
return mIsVisible;
}
+ boolean hasDirectActivity() {
+ return mHasDirectActivity;
+ }
+
@NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
@@ -120,6 +134,7 @@ class TaskContainer {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
mIsVisible = info.isVisible();
+ mHasDirectActivity = info.hasDirectActivity();
}
/**
@@ -184,11 +199,20 @@ class TaskContainer {
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+ boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
if (!includePin && isTaskFragmentContainerPinned(container)) {
continue;
}
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -211,9 +235,13 @@ class TaskContainer {
}
@Nullable
- Activity getTopNonFinishingActivity() {
+ Activity getTopNonFinishingActivity(boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
- final Activity activity = mContainers.get(i).getTopNonFinishingActivity();
+ final TaskFragmentContainer container = mContainers.get(i);
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
+ final Activity activity = container.getTopNonFinishingActivity();
if (activity != null) {
return activity;
}
@@ -221,6 +249,12 @@ class TaskContainer {
return null;
}
+ /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+ @Nullable
+ TaskFragmentContainer getOverlayContainer() {
+ return mOverlayContainer;
+ }
+
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
@@ -311,8 +345,8 @@ class TaskContainer {
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +366,15 @@ class TaskContainer {
}
private void onTaskFragmentContainerUpdated() {
+ // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+ // another special container that should also be on top in the future.
+ updateSplitPinContainerIfNecessary();
+ // Update overlay container after split pin container since the overlay should be on top of
+ // pin container.
+ updateOverlayContainerIfNecessary();
+ }
+
+ private void updateSplitPinContainerIfNecessary() {
if (mSplitPinContainer == null) {
return;
}
@@ -344,10 +387,7 @@ class TaskContainer {
}
// Ensure the pinned container is top-most.
- if (pinnedContainerIndex != mContainers.size() - 1) {
- mContainers.remove(pinnedContainer);
- mContainers.add(pinnedContainer);
- }
+ moveContainerToLastIfNecessary(pinnedContainer);
// Update the primary container adjacent to the pinned container if needed.
final TaskFragmentContainer adjacentContainer =
@@ -359,11 +399,48 @@ class TaskContainer {
}
}
- /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
- void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+ private void updateOverlayContainerIfNecessary() {
+ final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+ .filter(TaskFragmentContainer::isOverlay).toList();
+ if (overlayContainers.size() > 1) {
+ throw new IllegalStateException("There must be at most one overlay container per Task");
+ }
+ mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+ if (mOverlayContainer != null) {
+ moveContainerToLastIfNecessary(mOverlayContainer);
+ }
+ }
+
+ /** Moves the {@code container} to the last to align taskFragments' z-order. */
+ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+ final int index = mContainers.indexOf(container);
+ if (index < 0) {
+ Log.w(TAG, "The container:" + container + " is not in the container list!");
+ return;
+ }
+ if (index != mContainers.size() - 1) {
+ mContainers.remove(container);
+ mContainers.add(container);
+ }
+ }
+
+ /**
+ * Gets the descriptors of split states in this Task.
+ *
+ * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if
+ * any SplitContainer is in an intermediate state.
+ */
+ @Nullable
+ List<SplitInfo> getSplitStatesIfStable() {
+ final List<SplitInfo> splitStates = new ArrayList<>();
for (SplitContainer container : mSplitContainers) {
- outSplitStates.add(container.toSplitInfo());
+ final SplitInfo splitInfo = container.toSplitInfoIfStable();
+ if (splitInfo == null) {
+ return null;
+ }
+ splitStates.add(splitInfo);
}
+ return splitStates;
}
/** A wrapper class which contains the information of {@link TaskContainer} */
@@ -386,6 +463,15 @@ class TaskContainer {
return mConfiguration;
}
+ /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+ @NonNull
+ WindowMetrics getTaskMetrics() {
+ final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+ // TODO(b/190433398): Supply correct insets.
+ final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ }
+
/** Translates the given absolute bounds to relative bounds in this Task coordinate. */
void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
if (inOutBounds.isEmpty()) {
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 61df335515b8..3e7f99b96421 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@ class TaskFragmentContainer {
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -157,15 +160,32 @@ class TaskFragmentContainer {
*/
private boolean mHasCrossProcessActivities;
+ /** Whether this TaskFragment enable isolated navigation. */
+ private boolean mIsIsolatedNavigationEnabled;
+
+ /**
+ * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+ * TaskFragmentContainer, String)
+ */
+ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+ @Nullable Intent pendingAppearedIntent,
+ @NonNull TaskContainer taskContainer,
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+ controller, pairedPrimaryContainer, null /* overlayTag */);
+ }
+
/**
* 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
+ * @param overlayTag Sets to indicate this taskFragment is an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -174,6 +194,8 @@ class TaskFragmentContainer {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
+ mOverlayTag = overlayTag;
+
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -217,6 +239,30 @@ class TaskFragmentContainer {
/** List of non-finishing activities that belong to this container and live in this process. */
@NonNull
List<Activity> collectNonFinishingActivities() {
+ final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */);
+ if (activities == null) {
+ throw new IllegalStateException(
+ "Result activities should never be null when checkIfstable is false.");
+ }
+ return activities;
+ }
+
+ /**
+ * Collects non-finishing activities that belong to this container and live in this process.
+ *
+ * @param checkIfStable if {@code true}, returns {@code null} when the container is in an
+ * intermediate state.
+ * @return List of non-finishing activities that belong to this container and live in this
+ * process, {@code null} if checkIfStable is {@code true} and the container is in an
+ * intermediate state.
+ */
+ @Nullable
+ List<Activity> collectNonFinishingActivities(boolean checkIfStable) {
+ if (checkIfStable
+ && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) {
+ return null;
+ }
+
final List<Activity> allActivities = new ArrayList<>();
if (mInfo != null) {
// Add activities reported from the server.
@@ -224,6 +270,15 @@ class TaskFragmentContainer {
final Activity activity = mController.getActivity(token);
if (activity != null && !activity.isFinishing()) {
allActivities.add(activity);
+ } else {
+ if (checkIfStable) {
+ // Return null except for a special case when the activity is started in
+ // background.
+ if (activity == null && !mTaskContainer.isVisible()) {
+ continue;
+ }
+ return null;
+ }
}
}
}
@@ -277,9 +332,19 @@ class TaskFragmentContainer {
return false;
}
- @NonNull
- ActivityStack toActivityStack() {
- return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
+ /**
+ * Returns the ActivityStack representing this container.
+ *
+ * @return ActivityStack representing this container if it is in a stable state. {@code null} if
+ * in an intermediate state.
+ */
+ @Nullable
+ ActivityStack toActivityStackIfStable() {
+ final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */);
+ if (activities == null) {
+ return null;
+ }
+ return new ActivityStack(activities, isEmpty(), mToken);
}
/** Adds the activity that will be reparented to this container. */
@@ -743,6 +808,16 @@ class TaskFragmentContainer {
mLastCompanionTaskFragment = fragmentToken;
}
+ /** Returns whether to enable isolated navigation or not. */
+ boolean isIsolatedNavigationEnabled() {
+ return mIsIsolatedNavigationEnabled;
+ }
+
+ /** Sets whether to enable isolated navigation or not. */
+ void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+ mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -820,6 +895,20 @@ class TaskFragmentContainer {
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
+ /** Returns whether this taskFragment container is an overlay container. */
+ boolean isOverlay() {
+ return mOverlayTag != null;
+ }
+
+ /**
+ * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+ * taskFragment container is not an overlay container.
+ */
+ @Nullable
+ String getOverlayTag() {
+ return mOverlayTag;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -838,6 +927,7 @@ class TaskFragmentContainer {
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ + " overlayTag=" + mOverlayTag
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
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 a1fe7f75a826..9b84a48cdbda 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.isZero;
@@ -94,14 +95,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- /** Registers to listen to {@link CommonFoldingFeature} changes */
- public void addFoldingStateChangedCallback(
- java.util.function.Consumer<List<CommonFoldingFeature>> consumer) {
- synchronized (mLock) {
- mFoldingFeatureProducer.addDataChangedCallback(consumer);
- }
- }
-
/**
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
*
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2de245b..4ddbd13978d5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"truth",
"testables",
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 a0590dc2c832..9607b78bacf0 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
@@ -16,8 +16,10 @@
package androidx.window.extensions;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityTaskManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,7 +54,11 @@ public class WindowExtensionsTest {
@Test
public void testGetActivityEmbeddingComponent() {
- assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ } else {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
+ }
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 000000000000..50cfd941adb3
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+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;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+ private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+ new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+ private SplitController.ActivityStartMonitor mMonitor;
+
+ private Intent mIntent;
+
+ private TaskFragmentContainer mOverlayContainer1;
+
+ private TaskFragmentContainer mOverlayContainer2;
+
+ private Activity mActivity;
+ @Mock
+ private Resources mActivityResources;
+
+ @Mock
+ private WindowContainerTransaction mTransaction;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+ private SplitController mSplitController;
+ private SplitPresenter mSplitPresenter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mSplitController = new SplitController(mWindowLayoutComponent, producer);
+ mSplitPresenter = mSplitController.mPresenter;
+ mMonitor = mSplitController.getActivityStartMonitor();
+ mIntent = new Intent();
+
+ spyOn(mSplitController);
+ spyOn(mSplitPresenter);
+ spyOn(mMonitor);
+
+ doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ doReturn(mHandler).when(mSplitController).getHandler();
+ mActivity = createMockActivity();
+
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ @NonNull
+ private Activity createMockActivity() {
+ 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(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ return activity;
+ }
+
+ @Test
+ public void testOverlayCreateParamsFromBundle() {
+ assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+ assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+ .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+ }
+
+ @Test
+ public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+ verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+ anyInt(), any(), any());
+ }
+
+ @NonNull
+ private static Bundle createOverlayCreateParamsTestBundle() {
+ final Bundle bundle = new Bundle();
+
+ final Bundle paramsBundle = new Bundle();
+ paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+ TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+ paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+ paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+ TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+ bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+ return bundle;
+ }
+
+ @Test
+ public void testGetOverlayContainers() {
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+ final TaskFragmentContainer overlayContainer1 =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1);
+
+ assertThrows(
+ "The exception must throw if there are two overlay containers in the same task.",
+ IllegalStateException.class,
+ () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID + 1, "test3");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1, overlayContainer3);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+ assertThrows("The method must return null due to task mismatch between"
+ + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+ () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched to the same task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+ TASK_ID + 2);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched with the same tag as an existing overlay container in a different "
+ + "task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test1", bounds),
+ TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ + " is launched with the same tag and task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+ assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+ verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+ eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+ TASK_ID);
+
+ // OverlayContainer1 is dismissed since new container is launched in the same task with
+ // different tag. OverlayContainer2 is dismissed since new container is launched with the
+ // same tag in different task.
+ assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ private void createExistingOverlayContainers() {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+ List<TaskFragmentContainer> overlayContainers = mSplitController
+ .getAllOverlayTaskFragmentContainers();
+ assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ final Rect bounds = new Rect(TASK_BOUNDS);
+ bounds.offset(10, 10);
+ final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+ "test", bounds);
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ paramsOutsideTaskBounds, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+ assertThat(overlayContainer
+ .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ }
+
+ @Test
+ public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ // Add a SplitPinContainer, the overlay should be on top
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity);
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+ final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+ .getSplitPinContainer().getSecondaryContainer();
+
+ // Add a normal container after the overlay, the overlay should still on top,
+ // and the SplitPinContainer should also on top of the normal one.
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertThat(taskContainer.getTaskFragmentContainers())
+ .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+ .inOrder();
+
+ assertWithMessage("The pinned container must be returned excluding the overlay")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+ .isEqualTo(topPinnedContainer);
+
+ assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+ .isEqualTo(container);
+
+ assertWithMessage("The overlay container must be returned since it's always on top")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+ false /* includePin */, true /* includeOverlay */))
+ .isEqualTo(overlayContainer);
+ }
+
+ @Test
+ public void testGetTopNonFinishingActivityWithOverlay() {
+ createTestOverlayContainer(TASK_ID, "test1");
+ final Activity activity = createMockActivity();
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
+ final TaskContainer task = container.getTaskContainer();
+
+ assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+ assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
+ }
+
+ @Test
+ public void testUpdateContainer_dontInvokeUpdateOverlayForNonOverlayContainer() {
+ TaskFragmentContainer taskFragmentContainer = createMockTaskFragmentContainer(mActivity);
+
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+ verify(mSplitController, never()).updateOverlayContainer(any(), any());
+ }
+
+ @Test
+ public void testUpdateOverlayContainer_dismissOverlayIfNeeded() {
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+ assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
+
+ taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+
+ mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
+
+ assertWithMessage("The overlay must be dismissed since there's no activity"
+ + " in the task and other taskFragment.")
+ .that(taskContainer.getTaskFragmentContainers()).isEmpty();
+ }
+
+ /**
+ * A simplified version of {@link SplitController.ActivityStartMonitor
+ * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull OverlayCreateParams params, int taskId) {
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+ taskId, mIntent, mActivity);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
+ setupTaskFragmentInfo(container, activity);
+ return container;
+ }
+
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+ null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+ null /* pairedPrimaryContainer */, tag);
+ setupTaskFragmentInfo(overlayContainer, mActivity);
+ return overlayContainer;
+ }
+
+ private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ container.setInfo(mTransaction, info);
+ mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+ }
+}
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 b2ffad7a74e4..02031a67e7e3 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
@@ -48,18 +48,18 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.google.common.truth.Truth.assertWithMessage;
-
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.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
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.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -175,52 +175,6 @@ public class SplitControllerTest {
}
@Test
- public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = createTestTaskContainer();
- // tf1 has no running activity so is not active.
- final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
- // tf2 has running activity so is active.
- final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.addTaskFragmentContainer(tf2);
- // tf3 is finished so is not active.
- final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
- doReturn(true).when(tf3).isFinished();
- doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.addTaskFragmentContainer(tf3);
- mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
- assertWithMessage("Must return tf2 because tf3 is not active.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf3);
-
- assertWithMessage("Must return tf2 because tf2 has running activity.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf2);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(true).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
- + " creation.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- doReturn(false).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return null because tf1 becomes empty.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
- }
-
- @Test
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -305,7 +259,9 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+ TaskContainer taskContainer = tf.getTaskContainer();
+ spyOn(taskContainer);
+ verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
// Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -320,7 +276,7 @@ public class SplitControllerTest {
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer = mSplitController.getTaskContainer(TASK_ID);
taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -595,13 +551,12 @@ public class SplitControllerTest {
}
@Test
- public void testResolveStartActivityIntent_skipIfPinned() {
+ public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
- final TaskContainer taskContainer = container.getTaskContainer();
- spyOn(taskContainer);
+ container.setIsolatedNavigationEnabled(true);
+
final Intent intent = new Intent();
setupSplitRule(mActivity, intent);
- doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
mActivity));
}
@@ -634,7 +589,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
}
@Test
@@ -796,7 +752,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -838,7 +795,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -1181,7 +1139,7 @@ public class SplitControllerTest {
public void testOnTransactionReady_taskFragmentParentInfoChanged() {
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
- DEFAULT_DISPLAY, true);
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */);
transaction.addChange(new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(TASK_ID)
@@ -1286,6 +1244,34 @@ public class SplitControllerTest {
}
@Test
+ public void testSplitInfoCallback_NotReportSplitIfUnstable() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+
+ // Should report new SplitInfo list if stable.
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+
+ // Should not report new SplitInfo list if unstable, e.g. any Activity is finishing.
+ mSplitInfos.clear();
+ final Activity r2 = createMockActivity();
+ final Activity r3 = createMockActivity();
+ doReturn(true).when(r2).isFinishing();
+ addSplitTaskFragments(r2, r3);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertTrue(mSplitInfos.isEmpty());
+
+ // Should report SplitInfo list if it becomes stable again.
+ mSplitInfos.clear();
+ doReturn(false).when(r2).isFinishing();
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
public void testSplitInfoCallback_reportSplitInMultipleTasks() {
final int taskId0 = 1;
final int taskId1 = 2;
@@ -1537,9 +1523,9 @@ public class SplitControllerTest {
addSplitTaskFragments(primaryActivity, thirdActivity);
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
- assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
- assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
- == secondaryActivity);
+ assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+ assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+ .getTopNonFinishingActivity(), secondaryActivity);
}
/** Creates a mock activity in the organizer process. */
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 000c65a75c81..e56c8ab686e7 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
@@ -48,6 +48,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
/**
* Test class for {@link TaskContainer}.
*
@@ -77,14 +79,14 @@ public class TaskContainerTest {
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertEquals(WINDOWING_MODE_FREEFORM,
taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -104,13 +106,13 @@ public class TaskContainerTest {
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertFalse(taskContainer.isInPictureInPicture());
configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
- DEFAULT_DISPLAY, true /* visible */));
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
assertTrue(taskContainer.isInPictureInPicture());
}
@@ -149,20 +151,54 @@ public class TaskContainerTest {
@Test
public void testGetTopNonFinishingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
- assertNull(taskContainer.getTopNonFinishingActivity());
+ assertNull(taskContainer.getTopNonFinishingActivity(true /* includeOverlay */));
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
taskContainer.addTaskFragmentContainer(tf0);
final Activity activity0 = mock(Activity.class);
doReturn(activity0).when(tf0).getTopNonFinishingActivity();
- assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity0, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
taskContainer.addTaskFragmentContainer(tf1);
- assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity0, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
final Activity activity1 = mock(Activity.class);
doReturn(activity1).when(tf1).getTopNonFinishingActivity();
- assertEquals(activity1, taskContainer.getTopNonFinishingActivity());
+ assertEquals(activity1, taskContainer.getTopNonFinishingActivity(
+ true /* includeOverlay */));
+ }
+
+ @Test
+ public void testGetSplitStatesIfStable() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+
+ final SplitContainer splitContainer0 = mock(SplitContainer.class);
+ final SplitContainer splitContainer1 = mock(SplitContainer.class);
+ final SplitInfo splitInfo0 = mock(SplitInfo.class);
+ final SplitInfo splitInfo1 = mock(SplitInfo.class);
+ taskContainer.addSplitContainer(splitContainer0);
+ taskContainer.addSplitContainer(splitContainer1);
+
+ // When all the SplitContainers are stable, getSplitStatesIfStable() returns the list of
+ // SplitInfo representing the SplitContainers.
+ doReturn(splitInfo0).when(splitContainer0).toSplitInfoIfStable();
+ doReturn(splitInfo1).when(splitContainer1).toSplitInfoIfStable();
+
+ List<SplitInfo> splitInfoList = taskContainer.getSplitStatesIfStable();
+
+ assertEquals(2, splitInfoList.size());
+ assertEquals(splitInfo0, splitInfoList.get(0));
+ assertEquals(splitInfo1, splitInfoList.get(1));
+
+ // When any SplitContainer is in an intermediate state, getSplitStatesIfStable() returns
+ // null.
+ doReturn(null).when(splitContainer0).toSplitInfoIfStable();
+
+ splitInfoList = taskContainer.getSplitStatesIfStable();
+
+ assertNull(splitInfoList);
}
}
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 78b85e642c13..cc00a49604ee 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
@@ -321,6 +321,32 @@ public class TaskFragmentContainerTest {
}
@Test
+ public void testCollectNonFinishingActivities_checkIfStable() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+
+ // In case mInfo is null, collectNonFinishingActivities(true) should return null.
+ List<Activity> activities =
+ container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertNull(activities);
+
+ // collectNonFinishingActivities(true) should return proper value when the container is in a
+ // stable state.
+ final List<IBinder> runningActivities = Lists.newArrayList(mActivity.getActivityToken());
+ doReturn(runningActivities).when(mInfo).getActivities();
+ container.setInfo(mTransaction, mInfo);
+ activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertEquals(1, activities.size());
+
+ // In case any activity is finishing, collectNonFinishingActivities(true) should return
+ // null.
+ doReturn(true).when(mActivity).isFinishing();
+ activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+ assertNull(activities);
+ }
+
+ @Test
public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index e9abc7e522d5..fd4522e02438 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@ android_library {
static_libs: [
"androidx.appcompat_appcompat",
"androidx.core_core-animation",
+ "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
@@ -158,6 +159,7 @@ android_library {
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"iconloader_base",
+ "com_android_wm_shell_flags_lib",
"WindowManager-Shell-proto",
"dagger2",
"jsr330",
@@ -170,4 +172,5 @@ android_library {
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
}
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
new file mode 100644
index 000000000000..1a98ffcea9e7
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "com_android_wm_shell_flags",
+ package: "com.android.wm.shell",
+ srcs: [
+ "multitasking.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com_android_wm_shell_flags_lib",
+ aconfig_declarations: "com_android_wm_shell_flags",
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
new file mode 100644
index 000000000000..0e59e9ad744d
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -0,0 +1,30 @@
+package: "com.android.wm.shell"
+
+flag {
+ name: "example_flag"
+ namespace: "multitasking"
+ description: "An Example Flag"
+ bug: "300136750"
+}
+
+flag {
+ name: "enable_app_pairs"
+ namespace: "multitasking"
+ description: "Enables the ability to create and save app pairs to the Home screen"
+ bug: "274835596"
+}
+
+flag {
+ name: "enable_desktop_windowing"
+ namespace: "multitasking"
+ description: "Enables desktop windowing"
+ bug: "304778354"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_split_contextual"
+ namespace: "multitasking"
+ description: "Enables invoking split contextually"
+ bug: "276361926"
+}
diff --git a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
index c82a70c9a44e..5c5815818b8e 100644
--- a/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
+++ b/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto
@@ -48,7 +48,7 @@ message Transition {
optional int32 handler = 3;
optional int64 merge_time_ns = 4;
optional int64 merge_request_time_ns = 5;
- optional int32 merged_into = 6;
+ optional int32 merge_target = 6;
optional int64 abort_time_ns = 7;
}
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml
new file mode 100644
index 000000000000..65f5239737b2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_color_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_focused="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:state_selected="true"
+ android:color="@color/desktop_mode_maximize_menu_button_on_hover"/>
+ <item android:color="@color/desktop_mode_maximize_menu_button"/>
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml
new file mode 100644
index 000000000000..86679af5428b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/desktop_mode_maximize_menu_button_outline_color_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_hovered="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_focused="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:state_selected="true"
+ android:color="@color/desktop_mode_maximize_menu_button_outline_on_hover"/>
+ <item android:color="@color/desktop_mode_maximize_menu_button_outline"/>
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml
index 4ee10f429b37..15837adc2c77 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_handle_menu_background.xml
@@ -15,7 +15,8 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/white" />
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<corners android:radius="@dimen/desktop_mode_handle_menu_corner_radius" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
new file mode 100644
index 000000000000..5d9fe67e8bee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="@dimen/desktop_mode_maximize_menu_corner_radius" />
+</shape>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml
index c7aca1a72696..bfb0dd7f3100 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_maximize_button_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2023 The Android Open Source Project
~
@@ -11,14 +12,13 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
+ ~ limitations under the License
+-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.service">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.service"
- android:label="WindowManager Flicker Service Tests">
- </instrumentation>
-</manifest>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:radius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml
new file mode 100644
index 000000000000..6630fcab4794
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_left_button_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml
new file mode 100644
index 000000000000..7bd6e9981c12
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_maximize_menu_snap_right_button_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/desktop_mode_maximize_menu_button_color_selector"/>
+ <corners
+ android:topLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:topRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"
+ android:bottomLeftRadius="@dimen/desktop_mode_maximize_menu_buttons_small_corner_radius"
+ android:bottomRightRadius="@dimen/desktop_mode_maximize_menu_buttons_large_corner_radius"/>
+ <stroke android:width="1dp" android:color="@color/desktop_mode_maximize_menu_button_outline_color_selector"/>
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
index 3e0297ab612b..e9df936c3f94 100644
--- a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
+++ b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
@@ -14,8 +14,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<vector android:height="24dp" android:tint="#000000"
- android:viewportHeight="24" android:viewportWidth="24"
- android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17"/>
</vector>
+
diff --git a/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
new file mode 100644
index 000000000000..8ef3307ee875
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z"
+ android:fillColor="#455A64"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
new file mode 100644
index 000000000000..b489a5c1acd0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/bubble_popup_margin_horizontal"
+ android:layout_marginBottom="120dp"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_floating_landscape"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/bubble_bar_education_stack_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="center"
+ android:text="@string/bubble_bar_education_stack_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 10c9562cf651..d8ae9c8c64a6 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -26,7 +26,8 @@
android:id="@+id/bubble_manage_menu_dismiss_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
@@ -52,7 +53,8 @@
android:id="@+id/bubble_manage_menu_dont_bubble_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
@@ -78,7 +80,8 @@
android:id="@+id/bubble_manage_menu_settings_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="@dimen/bubble_menu_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
android:paddingStart="@dimen/bubble_menu_padding"
android:paddingEnd="@dimen/bubble_menu_padding"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 7e0c2071dc86..c525a297b2e0 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -31,35 +31,35 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="8dp">
+ android:paddingStart="16dp">
<ImageView
android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_margin="4dp"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_gravity="center_vertical"
android:contentDescription="@string/app_icon_text" />
<TextView
android:id="@+id/application_name"
android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_height="20dp"
android:minWidth="80dp"
android:textColor="@color/desktop_mode_caption_app_name_dark"
+ android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
- android:gravity="center_vertical"
+ android:lineHeight="20dp"
+ android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
tools:text="Gmail"/>
<ImageButton
android:id="@+id/expand_menu_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:padding="4dp"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
android:contentDescription="@string/expand_menu_text"
android:src="@drawable/ic_baseline_expand_more_24"
android:tint="@color/desktop_mode_caption_expand_button_dark"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index d93e9ba32105..7638132d6562 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -26,8 +26,8 @@
<ImageButton
android:id="@+id/caption_handle"
android:layout_width="128dp"
- android:layout_height="42dp"
- android:paddingVertical="19dp"
+ android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
+ android:paddingVertical="16dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
new file mode 100644
index 000000000000..c6f85a0b4ed4
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="@dimen/desktop_mode_handle_menu_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/app_info_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
+ android:layout_marginStart="1dp"
+ android:elevation="1dp"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_height="@dimen/desktop_mode_caption_icon_radius"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:contentDescription="@string/app_icon_text"/>
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ tools:text="Gmail"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:lineHeight="20dp"
+ android:textStyle="normal"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/collapse_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:layout_marginEnd="14dp"
+ android:layout_marginStart="14dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:rotation="180"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/windowing_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="horizontal"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background"
+ android:gravity="center_vertical">
+
+ <ImageButton
+ android:id="@+id/fullscreen_button"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/fullscreen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/split_screen_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/split_screen_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/floating_button"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:contentDescription="@string/float_button_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_floating"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ <ImageButton
+ android:id="@+id/desktop_button"
+ android:layout_marginStart="4dp"
+ android:contentDescription="@string/desktop_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_weight="1"
+ style="@style/DesktopModeHandleMenuWindowingButton"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/more_actions_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="vertical"
+ android:elevation="1dp"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/screenshot_button"
+ android:contentDescription="@string/screenshot_text"
+ android:text="@string/screenshot_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <Button
+ android:id="@+id/select_button"
+ android:contentDescription="@string/select_text"
+ android:text="@string/select_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+
+ </LinearLayout>
+</LinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
deleted file mode 100644
index 167a003932d6..000000000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_app_info_pill.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
- android:orientation="horizontal"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:gravity="center_vertical">
-
- <ImageView
- android:id="@+id/application_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginStart="14dp"
- android:layout_marginEnd="14dp"
- android:contentDescription="@string/app_icon_text"/>
-
- <TextView
- android:id="@+id/application_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- tools:text="Gmail"
- android:textColor="@color/desktop_mode_caption_menu_text_color"
- android:textSize="14sp"
- android:textFontWeight="500"
- android:lineHeight="20dp"
- android:textStyle="normal"
- android:layout_weight="1"/>
-
- <ImageButton
- android:id="@+id/collapse_menu_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:padding="4dp"
- android:layout_marginEnd="14dp"
- android:layout_marginStart="14dp"
- android:contentDescription="@string/collapse_menu_text"
- android:src="@drawable/ic_baseline_expand_more_24"
- android:rotation="180"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:background="?android:selectableItemBackgroundBorderless"/>
-</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
deleted file mode 100644
index 40a4b53f3e1d..000000000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_more_actions_pill.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
- android:orientation="vertical"
- android:background="@drawable/desktop_mode_decor_menu_background">
-
- <Button
- android:id="@+id/screenshot_button"
- android:contentDescription="@string/screenshot_text"
- android:text="@string/screenshot_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/close_button"
- android:contentDescription="@string/close_text"
- android:text="@string/close_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_close"
- android:drawableTint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
-</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
deleted file mode 100644
index 95283b9e214a..000000000000
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_windowing_pill.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/desktop_mode_handle_menu_width"
- android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
- android:orientation="horizontal"
- android:background="@drawable/desktop_mode_decor_menu_background"
- android:gravity="center_vertical">
-
- <ImageButton
- android:id="@+id/fullscreen_button"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/fullscreen_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/split_screen_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/split_screen_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_splitscreen"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/floating_button"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:contentDescription="@string/float_button_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_floating"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_inactive"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
- <ImageButton
- android:id="@+id/desktop_button"
- android:layout_marginStart="4dp"
- android:contentDescription="@string/desktop_text"
- android:src="@drawable/desktop_mode_ic_handle_menu_desktop"
- android:tint="@color/desktop_mode_caption_menu_buttons_color_active"
- android:layout_weight="1"
- style="@style/DesktopModeHandleMenuWindowingButton"/>
-
-</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
new file mode 100644
index 000000000000..0db72f7be8e6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:background="@drawable/desktop_mode_maximize_menu_background">
+
+
+ <Button
+ android:id="@+id/maximize_menu_maximize_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="120dp"
+ android:layout_height="80dp"
+ android:layout_marginRight="15dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_maximize_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_left_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="58dp"
+ android:layout_height="80dp"
+ android:layout_marginRight="6dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_snap_left_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_right_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="58dp"
+ android:layout_height="80dp"
+ android:color="@color/desktop_mode_maximize_menu_button"
+ android:background="@drawable/desktop_mode_maximize_menu_snap_right_button_background"
+ android:stateListAnimator="@null"/>
+</LinearLayout> \ 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 de4a225e41a7..202ea957971e 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Beweeg na regs bo"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Beweeg na links onder"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Beweeg na regs onder"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"vou <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> uit"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"vou <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> in"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-instellings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Maak borrel toe"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Moenie dat gesprek \'n borrel word nie"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Het dit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen onlangse borrels nie"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Onlangse borrels en borrels wat toegemaak is, sal hier verskyn"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Klets met borrels"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nuwe gesprekke verskyn as ikone in ’n hoek onderaan jou skerm. Tik om hulle uit te vou, of sleep om hulle toe te maak."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Beheer borrels enige tyd"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te bestuur watter apps en gesprekke in borrels kan verskyn"</string>
<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="4564728020654658478">"Tik om hierdie app te herbegin vir ’n beter aansig"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Verander hierdie app se aspekverhouding in Instellings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Verander aspekverhouding"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 21172e2267bc..4071e790d55a 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -32,23 +32,23 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"įˆ˜įŒ įŠ• į‹­į‰€į‹­įˆ©"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"įˆ˜į‰°įŒį‰ įˆŖį‹« įŠØį‰°įŠØįˆįˆˆ įˆ›į‹« ገጽ įŒ‹įˆ­ įˆ‹į‹­įˆ įˆ« į‹­į‰½įˆ‹įˆ"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"įˆ˜į‰°įŒį‰ įˆŖį‹« įŠØį‰°įŠØįˆįˆˆ įˆ›į‹« įŒˆį… įŒ‹įˆ­ įˆ‹į‹­įˆ įˆ« į‹­į‰½įˆ‹įˆ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"įˆ˜į‰°įŒį‰ įˆŖį‹«į‹ į‹Øį‰°įŠØįˆįˆˆ įˆ›į‹« įŒˆįŒ½įŠ• įŠ į‹­į‹°įŒįįˆ"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"į‹­įˆ… įˆ˜į‰°įŒį‰ įˆŖį‹« įˆ˜įŠØįˆį‰µ į‹Øįˆšį‰½įˆˆį‹ በ1 įˆ˜įˆµįŠ®į‰µ ቄቻ įŠį‹į¢"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"įˆ˜į‰°įŒį‰ įˆŖį‹« į‰ įˆįˆˆį‰°įŠ› įˆ›įˆ³į‹« įˆ‹į‹­ įˆ‹į‹­įˆ įˆ« į‹­į‰½įˆ‹įˆį¢"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"įˆ˜į‰°įŒį‰ įˆŖį‹« į‰ įˆįˆˆį‰°įŠ› įˆ›įˆ³į‹«į‹Žį‰½ įˆ‹į‹­ įˆ›įˆµįŒ€įˆ˜įˆ­įŠ• įŠ į‹­į‹°įŒįįˆį¢"</string>
- <string name="accessibility_divider" msgid="6407584574218956849">"į‹Øį‰°įŠØįˆįˆˆ į‹Øįˆ›į‹« ገጽ įŠØį‹į‹­"</string>
- <string name="divider_title" msgid="1963391955593749442">"į‹Øį‰°įŠØįˆįˆˆ į‹Øįˆ›į‹« ገጽ įŠØį‹į‹­"</string>
- <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"į‹ØįŒįˆ« įˆ™įˆ‰ įˆ›į‹« ገጽ"</string>
+ <string name="accessibility_divider" msgid="6407584574218956849">"į‹Øį‰°įŠØįˆįˆˆ į‹Øįˆ›į‹« įŒˆį… įŠØį‹į‹­"</string>
+ <string name="divider_title" msgid="1963391955593749442">"į‹Øį‰°įŠØįˆįˆˆ į‹Øįˆ›į‹« įŒˆį… įŠØį‹į‹­"</string>
+ <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"į‹ØįŒįˆ« įˆ™įˆ‰ įˆ›į‹« įŒˆį…"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"įŒįˆ« 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"įŒįˆ« 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"įŒįˆ« 30%"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"į‹Øį‰€įŠ įˆ™įˆ‰ įˆ›į‹« ገጽ"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"į‹Øįˆ‹į‹­ įˆ™įˆ‰ įˆ›į‹« ገጽ"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"į‹Øį‰€įŠ įˆ™įˆ‰ įˆ›į‹« įŒˆį…"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"į‹Øįˆ‹į‹­ įˆ™įˆ‰ įˆ›į‹« įŒˆį…"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"įŠØįˆ‹į‹­ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"įŠØįˆ‹į‹­ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"įŠØįˆ‹į‹­ 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች įˆ™įˆ‰ įˆ›į‹« ገጽ"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች įˆ™įˆ‰ įˆ›į‹« įŒˆį…"</string>
<string name="accessibility_split_left" msgid="1713683765575562458">"į‹ˆį‹° įŒįˆ« įŠØį‹įįˆ"</string>
<string name="accessibility_split_right" msgid="8441001008181296837">"į‹ˆį‹° į‰€įŠ įŠØį‹įįˆ"</string>
<string name="accessibility_split_top" msgid="2789329702027147146">"į‹ˆį‹° įˆ‹į‹­ įŠØį‹įįˆ"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"į‹ˆį‹° įˆ‹į‹­įŠ›į‹ į‰€įŠ įŠ įŠ•į‰€įˆ³į‰…įˆµ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"į‹ØįŒįˆ­įŒŒį‹įŠ• įŒįˆ« įŠ įŠ•į‰€įˆ³į‰…įˆµ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"į‰³į‰½įŠ›į‹įŠ• į‰€įŠ į‹«įŠ•į‰€įˆ³į‰…įˆ±"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>įŠ• į‹˜įˆ­įŒ‹"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>įŠ• įˆ°į‰„įˆµį‰„"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"የ<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> į‰…įŠ•į‰„įˆ®į‰½"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"įŠ įˆØį‹įŠ• įŠ įˆ°įŠ“į‰„į‰µ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"į‹į‹­į‹­į‰¶į‰½įŠ• į‰ įŠ įˆØį‹ įŠ į‰³įˆ³į‹­"</string>
@@ -76,16 +78,22 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"įŒˆį‰£įŠ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"įˆįŠ•įˆ į‹Øį‰…įˆ­į‰„ įŒŠį‹œ įŠ įˆØį‹į‹Žį‰½ į‹Øįˆ‰įˆ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"į‹Øį‰…įˆ­į‰„ įŒŠį‹œ įŠ įˆØį‹į‹Žį‰½ įŠ„įŠ“ į‹Øį‰°įˆ°įŠ“į‰ į‰± įŠ įˆØį‹į‹Žį‰½ įŠ„į‹šįˆ… ቄቅ į‹­įˆ‹įˆ‰"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"įŠ įˆØį‹į‹Žį‰½įŠ• į‰ įˆ˜įŒ į‰€įˆ į‹­į‹ˆį‹«į‹©"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"įŠ į‹²įˆµ į‹į‹­į‹­į‰¶į‰½ į‰ įˆ›į‹« įŒˆį…į‹Ž į‹Øį‰³į‰½įŠ›į‹ įŒ„įŒ į‹įˆµįŒ„ įŠ„įŠ•į‹° įŠ į‹¶į‹Žį‰½ į‹­į‰³į‹«įˆ‰į¢ įˆˆįˆ˜į‹˜įˆ­įŒ‹į‰µ įˆ˜į‰³ į‹«į‹µįˆ­įŒ“į‰øį‹ į‹ˆį‹­įˆ įˆˆįˆ›įˆ°įŠ“į‰ į‰µ į‹­įŒŽį‰µį‰·į‰øį‹į¢"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"į‰ įˆ›įŠ•įŠ›į‹įˆ įŒŠį‹œ į‹“įˆØį‹į‹Žį‰½įŠ• į‹­į‰†įŒ£įŒ įˆ©"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"į‹Øį‰µįŠ›į‹Žį‰¹ įˆ˜į‰°įŒį‰ įˆŖį‹«į‹Žį‰½ įŠ„įŠ“ į‹į‹­į‹­į‰¶į‰½ į‹“įˆØį‹ įˆ˜įįŒ įˆ­ įŠ„įŠ•į‹°įˆšį‰½įˆ‰ įˆˆįˆ›įˆµį‰°į‹³į‹°įˆ­ įŠ„į‹šįˆ… įŒ‹įˆ­ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"įŠ įˆØį‹"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"į‹«į‰€įŠ“į‰„įˆ©"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"įŠ įˆØį‹ į‰°įˆ°įŠ“į‰„į‰·įˆį¢"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"įˆˆį‰°įˆ»įˆˆ ዕይታ į‹­įˆ…įŠ• įˆ˜į‰°įŒį‰ įˆŖį‹« į‹³įŒįˆ įˆˆįˆ›įˆµįŠįˆ³į‰µ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰į¢"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"įˆˆį‰°įˆ»įˆˆ ዕይታ į‹­įˆ…įŠ• įˆ˜į‰°įŒį‰ įˆŖį‹« įŠ„įŠ•į‹°įŒˆįŠ“ įˆˆįˆ˜įŒ€įˆ˜įˆ­ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"į‹Øį‹šįˆ…įŠ• įˆ˜į‰°įŒį‰ įˆŖį‹« įˆįŒ„įŒ„įŠ įŒˆį…į‰³ į‰ į‰…įŠ•į‰„įˆ®į‰½ į‹įˆµįŒ„ į‹­įˆˆį‹įŒ”"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"į‹ØįˆįŒ„įŒ„įŠ įŒˆį…į‰³ įˆˆį‹įŒ„"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"į‹ØįŠ«įˆœįˆ« į‰½įŒįˆ®į‰½ įŠ įˆ‰?\nį‹³įŒįˆ įˆˆįˆ›į‰ įŒ€į‰µ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"įŠ įˆį‰°įˆµį‰°įŠ«įŠØįˆˆįˆ?\nįˆˆįˆ›įˆ…į‹°įˆ­ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"įˆįŠ•įˆ į‹ØįŠ«įˆœįˆ« į‰½įŒįˆ®į‰½ į‹Øįˆ‰įˆ? įˆˆįˆ›įˆ°įŠ“į‰ į‰µ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰į¢"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"į‰°įŒØįˆ›įˆŖ į‹­įˆ˜įˆįŠØį‰± įŠ„įŠ“ į‹«į‹µįˆ­įŒ‰"</string>
- <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"įˆˆį‰°įŠØįˆįˆˆ įˆ›į‹« ገጽ įˆŒįˆ‹ įˆ˜į‰°įŒį‰ įˆŖį‹« į‹­įŒŽį‰µį‰±"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"į‰¦į‰³į‹įŠ• įˆˆįˆ˜į‰€į‹Øįˆ­ įŠØįˆ˜į‰°įŒį‰ įˆŖį‹«į‹ į‹įŒŖ įˆįˆˆį‰“ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"įˆˆį‰°įŠØįˆįˆˆ įˆ›į‹« įŒˆį… įˆŒįˆ‹ įˆ˜į‰°įŒį‰ įˆŖį‹« į‹­įŒŽį‰µį‰±"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"į‰¦į‰³į‹įŠ• įˆˆįˆ˜į‰€į‹Øįˆ­ įŠØįˆ˜į‰°įŒį‰ įˆŖį‹«į‹ į‹įŒ­ įˆįˆˆį‰“ įˆ˜į‰³ į‹«į‹µįˆ­įŒ‰"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"įŒˆį‰£įŠ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"įˆˆį‰°įŒØįˆ›įˆŖ መረጃ į‹­į‹˜įˆ­įŒ‰į¢"</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"įˆˆį‰°įˆ»įˆˆ ዕይታ įŠ„įŠ•į‹°įŒˆįŠ“ į‹­įŒ€įˆ˜įˆ­?"</string>
@@ -102,11 +110,11 @@
<string name="app_icon_text" msgid="2823268023931811747">"į‹Øįˆ˜į‰°įŒį‰ įˆŖį‹« įŠ į‹¶"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"įˆ™įˆ‰ įˆ›į‹«"</string>
<string name="desktop_text" msgid="1077633567027630454">"į‹Øį‹“įˆµįŠ­į‰¶į• įˆįŠį‰³"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"į‹Øį‰°įŠØįˆįˆˆ įˆ›į‹« ገጽ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"į‹Øį‰°įŠØįˆįˆˆ įˆ›į‹« įŒˆį…"</string>
<string name="more_button_text" msgid="3655388105592893530">"į‰°įŒØįˆ›įˆŖ"</string>
<string name="float_button_text" msgid="9221657008391364581">"į‰°įŠ•įˆ³į‹įŠ"</string>
<string name="select_text" msgid="5139083974039906583">"įˆįˆØįŒ„"</string>
- <string name="screenshot_text" msgid="1477704010087786671">"į‰…įŒ½į‰ į‰³į‹Š ገጽ įŠ„į‹­į‰³"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"į‰…įŒ½į‰ į‰³į‹Š įŒˆį… ዕይታ"</string>
<string name="close_text" msgid="4986518933445178928">"į‹įŒ‹"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"įˆįŠ“įˆŒ į‹įŒ‹"</string>
<string name="expand_menu_text" msgid="3847736164494181168">"įˆįŠ“įˆŒįŠ• įŠ­įˆį‰µ"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index a6be57889a4e..84c1c6763d43 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -20,7 +20,7 @@
<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_fullscreen" msgid="7278047353591302554">"įˆ™įˆ‰ įˆ›į‹« ገጽ"</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>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index a714b49d1305..d3890a779f87 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"الانتقال ؄لى أعلى Ų§Ł„ŁŠŲ³Ų§Ų±"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"نقل ؄لى أسفل ŁŠŁ…ŁŠŁ† الؓاؓة"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نقل ؄لى أسفل Ų§Ł„ŁŠŲ³Ų§Ų±"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"توسيع <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"تصغير <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ų„Ų¹ŲÆŲ§ŲÆŲ§ŲŖ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"؄غلاق فقاعة المحادثة"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ų¹ŲÆŁ… Ų¹Ų±Ų¶ المحادثة ŁƒŁŁ‚Ų§Ų¹Ų© Ł…Ų­Ų§ŲÆŲ«Ų©"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"حسنًا"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ł„ŁŠŲ³ Ł‡Ł†Ų§Łƒ فقاعات Ł…Ų­Ų§ŲÆŲ«Ų§ŲŖ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ستظهر هنا Ų£Ų­ŲÆŲ« فقاعات المحادثات ŁˆŁŁ‚Ų§Ų¹Ų§ŲŖ المحادثات Ų§Ł„ŲŖŁŠ ŲŖŁ… ؄غلاقها."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"الدردؓة ŲØŲ§Ų³ŲŖŲ®ŲÆŲ§Ł… فقاعات المحادثات"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"تظهر المحادثات Ų§Ł„Ų¬ŲÆŁŠŲÆŲ© في Ų“ŁƒŁ„ Ų±Ł…ŁˆŲ² بأسفل Ų£Ų­ŲÆ Ų¬Ų§Ł†ŲØŁŽŁŠ الؓاؓة. انقر على الرمز Ł„ŲŖŁˆŲ³ŁŠŲ¹ المحادثة أو اسحبه ل؄خفائها."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ų§Ł„ŲŖŲ­ŁƒŁ‘Ł… في ؄ظهار الفقاعات في أي ŁˆŁ‚ŲŖ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"انقر هنا Ł„Ł„ŲŖŲ­ŁƒŁ‘Ł… في ؄ظهار فقاعات Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚Ų§ŲŖ ŁˆŲ§Ł„Ł…Ų­Ų§ŲÆŲ«Ų§ŲŖ Ų§Ł„ŲŖŁŠ ŲŖŲ±ŁŠŲÆŁ‡Ų§."</string>
<string name="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="4564728020654658478">"انقر ل؄عادة ŲŖŲ“ŲŗŁŠŁ„ هذا Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚ Ł„Ł„Ų­ŲµŁˆŁ„ على ŲŖŲ¬Ų±ŲØŲ© Ų¹Ų±Ų¶ أفضل."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ŁŠŁ…ŁƒŁ†Łƒ تغيير نسبة العرض ؄لى الارتفاع لهذا Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚ من خلال \"ال؄عدادات\"."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغيير نسبة العرض ؄لى الارتفاع"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل Ł‡Ł†Ų§Łƒ Ł…Ų“Ų§ŁƒŁ„ في Ų§Ł„ŁƒŲ§Ł…ŁŠŲ±Ų§ŲŸ\nانقر ل؄عادة الضبط."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم ŁŠŲŖŁ… Ų­Ł„ Ų§Ł„Ł…Ų“ŁƒŁ„Ų©ŲŸ\nانقر Ł„Ł„Ų¹ŁˆŲÆŲ©"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ų£Ł„ŁŠŲ³ Ł‡Ł†Ų§Łƒ Ł…Ų“Ų§ŁƒŁ„ في Ų§Ł„ŁƒŲ§Ł…ŁŠŲ±Ų§ŲŸ انقر لل؄غلاق."</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 6d86747ef336..05b8f7dca729 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ą¦¶ą§€ą§°ą§ą¦·ą§° সোঁফালে নিয়ক"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¦¬ą§ą¦Ÿą¦¾ą¦®ą¦Ÿą§‹ বাওঁফালে নিয়ক"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"তলৰ সোঁফালে নিয়ক"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¦¬ą¦æą¦øą§ą¦¤ą¦¾ą§° কৰক"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¦øą¦‚ą¦•ą§‹ą¦šą¦Ø কৰক"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą¦›ą§‡ą¦Ÿą¦æą¦‚"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল ą¦…ą¦—ą§ą§°ą¦¾ą¦¹ą§ą¦Æ কৰক"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą¦¬ą¦¾ą§°ą§ą¦¤ą¦¾ą¦²ą¦¾ą¦Ŗ বাবল নকৰিব"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"বুজি পালোঁ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনো শেহতীয়া bubbles নাই"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"শেহতীয়া bubbles আৰু ą¦…ą¦—ą§ą§°ą¦¾ą¦¹ą§ą¦Æ কৰা bubbles ইয়াত ą¦Ŗą§ą§°ą¦¦ą¦°ą§ą¦¶ą¦æą¦¤ হ\'ব"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bubbles ą¦¬ą§ą¦Æą§±ą¦¹ą¦¾ą§° কৰি চাট কৰক"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"নতুন ą¦¬ą¦¾ą§°ą§ą¦¤ą¦¾ą¦²ą¦¾ą¦Ŗą¦øą¦®ą§‚ą¦¹ আপোনাৰ ą¦øą§ą¦•ą§ą§°ą§€ą¦Øą§° ą¦ą¦•ą§‡ą¦¬ą¦¾ą§°ą§‡ তলৰ ą¦ą¦Ÿą¦¾ কোণত ą¦šą¦æą¦¹ą§ą¦Ø ą¦¹ą¦æą¦šą¦¾ą¦Ŗą§‡ দেখা পোৱা ą¦Æą¦¾ą¦Æą¦¼ą„¤ সেইসমূহ ą¦¬ą¦æą¦øą§ą¦¤ą¦¾ą§° ą¦•ą§°ą¦æą¦¬ą¦²ą§ˆ ą¦Ÿą¦æą¦Ŗą¦• বা ą¦…ą¦—ą§ą§°ą¦¾ą¦¹ą§ą¦Æ ą¦•ą§°ą¦æą¦¬ą¦²ą§ˆ টানি আনি ą¦ą§°ą¦•ą„¤"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যিকোনো সময়তে বাবল ą¦Øą¦æą¦Æą¦¼ą¦Øą§ą¦¤ą§ą§°ą¦£ কৰক"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোনবোৰ ą¦ą¦Ŗą§ā€Œ আৰু ą¦¬ą¦¾ą§°ą§ą¦¤ą¦¾ą¦²ą¦¾ą¦Ŗ বাবল হ’ব পাৰে সেয়া পৰিচালনা ą¦•ą§°ą¦æą¦¬ą¦²ą§ˆ ইয়াত ą¦Ÿą¦æą¦Ŗą¦•"</string>
<string name="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="4564728020654658478">"ą¦‰ą¦Øą§ą¦Øą¦¤ ভিউ পোৱাৰ বাবে ą¦ą¦Ŗą§ā€Œą¦Ÿą§‹ ą§°ą¦æą¦·ą§ą¦Ÿą¦¾ą§°ą§ą¦Ÿ ą¦•ą§°ą¦æą¦¬ą¦²ą§ˆ ą¦Ÿą¦æą¦Ŗą¦•"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¦›ą§‡ą¦Ÿą¦æą¦™ą¦²ą§ˆ ą¦—ą§ˆ ą¦ą¦‡ ą¦ą¦Ŗą§ā€Œą¦Ÿą§‹ą§° আকাৰৰ অনুপাত সলনি কৰক"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"আকাৰৰ অনুপাত সলনি কৰক"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো ą¦øą¦®ą¦øą§ą¦Æą¦¾ ą¦¹ą§ˆą¦›ą§‡ নেকি?\nপুনৰ খাপ ą¦–ą§‹ą§±ą¦¾ą¦¬ą¦²ą§ˆ ą¦Ÿą¦æą¦Ŗą¦•"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¦ą¦‡ą¦Ÿą§‹ সমাধান কৰা নাই নেকি?\ną¦Ŗą§‚ą§°ą§ą¦¬ą¦¾ą§±ą¦øą§ą¦„ą¦¾ą¦²ą§ˆ নিবলৈ ą¦Ÿą¦æą¦Ŗą¦•"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো ą¦øą¦®ą¦øą§ą¦Æą¦¾ নাই নেকি? ą¦…ą¦—ą§ą§°ą¦¾ą¦¹ą§ą¦Æ ą¦•ą§°ą¦æą¦¬ą¦²ą§ˆ ą¦Ÿą¦æą¦Ŗą¦•ą„¤"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 7c662827cca7..108593e4b948 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Yuxarıya sağa köçürün"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Aşağıya sola köçürün"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Aşağıya sağa köçürün"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"genişləndirin: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"yığcamlaşdırın: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Yumrucuğu ləğv edin"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Sƶhbəti yumrucuqda gƶstərmə"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Yumrucuqlar yoxdur"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son yumrucuqlar və buraxılmış yumrucuqlar burada gƶrünəcək"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Yumrucuqlar vasitəsilə sƶhbət edin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yeni sƶhbətlər ekranın aşağı küncündə ikonalar kimi gƶrünür. Toxunaraq genişləndirin, yaxud sürüşdürərək imtina edin."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Yumrucuqları idarə edin"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bura toxunaraq yumrucuq gƶstərəcək tətbiq və sƶhbətləri idarə edin"</string>
<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="4564728020654658478">"Yaxşı gƶrünüş üçün toxunaraq bu tətbiqi yenidən başladın"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ayarlarda bu tətbiqin tərəflər nisbətini dəyişin"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tərəflər nisbətini dəyişin"</string>
<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>
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 8de9d11def2b..76fd5b1c2d67 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premesti gore desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premesti dole levo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premesti dole desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proÅ”irite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skupite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"PodeŔavanja za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne koristi oblačiće za konverzaciju"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Važi"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovde se prikazuju nedavni i odbačeni oblačići"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Ćaskajte u oblačićima"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nove konverzacije se pojavljuju kao ikone u donjem uglu ekrana. Dodirnite da biste ih proŔirili ili prevucite da biste ih odbacili."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"KontroliÅ”ite oblačiće u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovde i odredite koje aplikacije i konverzacije mogu da imaju oblačić"</string>
<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="4564728020654658478">"Dodirnite da biste restartovali ovu aplikaciju radi boljeg prikaza"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promenite razmeru ove aplikacije u PodeŔavanjima"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promeni razmeru"</string>
<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>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ProŔirite za joŔ informacija."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim Ŕto možete da izgubite ono Ŕto ste uradili ili nesačuvane promene, ako ih ima"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, ali možete da izgubite napredak ili nesačuvane promene"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 3d99514e2172..473d15acc632 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŠµŃ€Š°Š¼ŃŃŃ†Ń–Ń†Šµ правей і Š²Ń‹ŃˆŃŠ¹"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŠµŃ€Š°Š¼ŃŃŃ†Ń–Ń†ŃŒ Š»ŃŠ²ŠµŠ¹ і Š½Ń–Š¶ŃŠ¹"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŠµŃ€Š°Š¼ŃŃŃ†Ń–Ń†ŃŒ правей і Š½Ń–Š¶ŃŠ¹"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: Ń€Š°Š·Š³Š°Ń€Š½ŃƒŃ†ŃŒ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: Š·Š³Š°Ń€Š½ŃƒŃ†ŃŒ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ŠŠ°Š»Š°Š“Ń‹ \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŠŠ“Ń…Ń–Š»Ń–Ń†ŃŒ Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½Šµ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ŠŠµ ŠæŠ°ŠŗŠ°Š·Š²Š°Ń†ŃŒ Ń€Š°Š·Š¼Š¾Š²Ńƒ ў Š²Ń‹Š³Š»ŃŠ“Š·Šµ ŃžŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Ń… Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½ŃŃž"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Š—Ń€Š°Š·ŃƒŠ¼ŠµŠ»Š°"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŃŠ¼Š° Š½ŃŠ“Š°ŃžŠ½Ń–Ń… ŃƒŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Ń… Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½ŃŃž"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ŠŃŠ“Š°ŃžŠ½Ń–Ń і Š°Š“Ń…Ń–Š»ŠµŠ½Ń‹Ń ŃžŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Ń Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½Ń– Š±ŃƒŠ“ŃƒŃ†ŃŒ паказаны тут"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Чат Š· выкарыстаннем ŃƒŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Ń… Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½ŃŃž"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²Ń‹Ń размовы ŠæŠ°ŠŗŠ°Š·Š²Š°ŃŽŃ†Ń†Š° ў Š²Ń‹Š³Š»ŃŠ“Š·Šµ Š·Š½Š°Ń‡ŠŗŠ¾Ńž у ніжнім вугле ŃŠŗŃ€Š°Š½Š°. ŠŠ°Ń†Ń–ŃŠ½Ń–Ń†Šµ на іх, каб Ń€Š°Š·Š³Š°Ń€Š½ŃƒŃ†ŃŒ. ŠŸŠµŃ€Š°Ń†ŃŠ³Š½Ń–Ń†Šµ іх, калі хочаце Š·Š°ŠŗŃ€Ń‹Ń†ŃŒ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŠšŃ–Ń€ŃƒŠ¹Ń†Šµ налаГамі ŃžŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Ń… Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½ŃŃž у Š»ŃŽŠ±Ń‹ час"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Каб ŠŗŃ–Ń€Š°Š²Š°Ń†ŃŒ ŃƒŃŠæŠ»Ń‹Š²Š°Š»ŃŒŠ½Ń‹Š¼Ń– Š°ŠæŠ°Š²ŃŃˆŃ‡ŃŠ½Š½ŃŠ¼Ń– Š“Š»Ń праграм і Ń€Š°Š·Š¼Š¾Ńž, націсніце тут"</string>
<string name="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="4564728020654658478">"ŠŠ°Ń†Ń–ŃŠ½Ń–Ń†Šµ, каб ŠæŠµŃ€Š°Š·Š°ŠæŃƒŃŃ†Ń–Ń†ŃŒ Š³ŃŃ‚Ńƒ ŠæŃ€Š°Š³Ń€Š°Š¼Ńƒ Š“Š»Ń Š·Ń€ŃƒŃ‡Š½ŠµŠ¹ŃˆŠ°Š³Š° ŠæŃ€Š°Š³Š»ŃŠ“Ńƒ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Š—Š¼ŃŠ½Ń–Ń†ŃŒ ŃŃƒŠ°Š“Š½Š¾ŃŃ–Š½Ń‹ Š±Š°ŠŗŠ¾Ńž Š“Š»Ń Š³ŃŃ‚Š°Š¹ праграмы ў налаГах"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Š—Š¼ŃŠ½Ń–Ń†ŃŒ ŃŃƒŠ°Š“Š½Š¾ŃŃ–Š½Ń‹ Š±Š°ŠŗŠ¾Ńž"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠŸŃ€Š°Š±Š»ŠµŠ¼Ń‹ Š· камерай?\nŠŠ°Ń†Ń–ŃŠ½Ń–Ń†Šµ, каб ŠæŠµŃ€Š°Š°Š±ŃŃ‚Š°Š»ŃŠ²Š°Ń†ŃŒ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŠµ ŃžŠ“Š°Š»Š¾ŃŃ Š²Ń‹ŠæŃ€Š°Š²Ń–Ń†ŃŒ?\nŠŠ°Ń†Ń–ŃŠ½Ń–Ń†Šµ, каб Š°Š“Š½Š°Š²Ń–Ń†ŃŒ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŃ–ŃŠŗŃ–Ń… праблем Š· камерай? ŠŠ°Ń†Ń–ŃŠ½Ń–Ń†Šµ, каб Š°Š“Ń…Ń–Š»Ń–Ń†ŃŒ."</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 0473f270239a..7aa98e5bc218 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Š²Š°Š½Šµ горе Š²Š“ŃŃŠ½Š¾"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Š²Š°Š½Šµ Голу Š²Š»ŃŠ²Š¾"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Š²Š°Š½Šµ Голу Š²Š“ŃŃŠ½Š¾"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Ń€Š°Š·Š³ŃŠŠ²Š°Š½Šµ на <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"свиване на <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠø за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŠžŃ‚Ń…Š²ŃŠŃ€Š»ŃŠ½Šµ на балончетата"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Без балончета за разговора"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Разбрах"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŃŠ¼Š° ŃŠŗŠ¾Ń€Š¾ŃˆŠ½Šø балончета"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Š”ŠŗŠ¾Ń€Š¾ŃˆŠ½ŠøŃ‚Šµ Šø Š¾Ń‚Ń…Š²ŃŠŃ€Š»ŠµŠ½ŠøŃ‚Šµ балончета ще се показват Ń‚ŃƒŠŗ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Чат с балончета"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²ŠøŃ‚Šµ разговори се показват като икони в Š“Š¾Š»Š½ŠøŃ ъгъл на екрана ви. Докоснете, за Га ги Ń€Š°Š·Š³ŃŠŠ½ŠµŃ‚Šµ, или ги ŠæŠ»ŃŠŠ·Š½ŠµŃ‚Šµ за Š¾Ń‚Ń…Š²ŃŠŃ€Š»ŃŠ½Šµ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Управление на балончетата по Š²ŃŃŠŗŠ¾ време"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Докоснете Ń‚ŃƒŠŗ, за Га ŃƒŠæŃ€Š°Š²Š». кои прил. Šø разговори могат Га показват балончета"</string>
<string name="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="4564728020654658478">"Докоснете, за Га рестартирате това приложение с цел по-Š“Š¾Š±ŃŠŃ€ изглеГ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ŠŸŃ€Š¾Š¼ŠµŠ½ŠµŃ‚Šµ ŃŃŠŠ¾Ń‚Š½Š¾ŃˆŠµŠ½ŠøŠµŃ‚Š¾ на това приложение в ā€žŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠøā€œ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ŠŸŃ€Š¾Š¼ŃŠ½Š° на ŃŃŠŠ¾Ń‚Š½Š¾ŃˆŠµŠ½ŠøŠµŃ‚Š¾"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Š˜Š¼Š°Ń‚Šµ проблеми с камерата?\nДокоснете за ремонтиране"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼ŃŠŃ‚ не се отстрани?\nДокоснете за Š²Ń€ŃŠŃ‰Š°Š½Šµ в ŠæŃ€ŠµŠ“ŠøŃˆŠ½Š¾Ń‚Š¾ ŃŃŠŃŃ‚Š¾ŃŠ½ŠøŠµ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŃŠ¼Š°Ń‚Šµ проблеми с камерата? Докоснете, за Га Š¾Ń‚Ń…Š²ŃŠŃ€Š»ŠøŃ‚Šµ."</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4fe1be0c455e..caad87e8f05f 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"উপরে ঔানদিকে সরান"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¦Øą¦æą¦šą§‡ বাঁদিকে সরান"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą¦Øą¦æą¦šą§‡ ঔান দিকে সরান"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> বঔ় করুন"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> আঔ়াল করুন"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą¦øą§‡ą¦Ÿą¦æą¦‚ą¦ø"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল ą¦–ą¦¾ą¦°ą¦æą¦œ করুন"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ক঄োপক঄ন বাবল হিসেবে দেখাবে না"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¦¬ą§ą¦ą§‡ą¦›ą¦æ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"কোনও ą¦øą¦¾ą¦®ą§ą¦Ŗą§ą¦°ą¦¤ą¦æą¦• বাবল নেই"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą¦øą¦¾ą¦®ą§ą¦Ŗą§ą¦°ą¦¤ą¦æą¦• ও বাতিল করা বাবল ą¦ą¦–ą¦¾ą¦Øą§‡ দেখা যাবে"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"বাবল ą¦¬ą§ą¦Æą¦¬ą¦¹ą¦¾ą¦° করে ą¦šą§ą¦Æą¦¾ą¦Ÿ করুন"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"নতুন ক঄োপক঄ন, আপনার ą¦øą§ą¦•ą§ą¦°ą¦æą¦Øą§‡ą¦° ą¦Øą¦æą¦šą§‡ কোণার দিকে আইকন হিসেবে ą¦¦ą§‡ą¦–ą¦¾ą¦Æą¦¼ą„¤ ą¦øą§‡ą¦Ÿą¦æ বঔ় করতে ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ করুন বা বাতিল করতে ą¦Ÿą§‡ą¦Øą§‡ ą¦†ą¦Øą§ą¦Øą„¤"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"যেকোনও সময় বাবল ą¦Øą¦æą¦Æą¦¼ą¦Øą§ą¦¤ą§ą¦°ą¦£ করুন"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"কোন ą¦…ą§ą¦Æą¦¾ą¦Ŗ ও ক঄োপক঄নের ą¦œą¦Øą§ą¦Æ বাবলের সুবিধা চান তা ą¦®ą§ą¦Æą¦¾ą¦Øą§‡ą¦œ করতে ą¦ą¦–ą¦¾ą¦Øą§‡ ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ করুন"</string>
<string name="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="4564728020654658478">"আরও ভাল ভিউয়ের ą¦œą¦Øą§ą¦Æ ą¦ą¦‡ ą¦…ą§ą¦Æą¦¾ą¦Ŗ ą¦°ą¦æą¦øą§ą¦Ÿą¦¾ą¦°ą§ą¦Ÿ করতে ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ করুন"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¦øą§‡ą¦Ÿą¦æą¦‚ą¦ø ঄েকে ą¦ą¦‡ ą¦…ą§ą¦Æą¦¾ą¦Ŗą§‡ą¦° ą¦…ą§ą¦Æą¦¾ą¦øą§ą¦Ŗą§‡ą¦•ą§ą¦Ÿ রেশিও ą¦Ŗą¦°ą¦æą¦¬ą¦°ą§ą¦¤ą¦Ø করুন"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¦…ą§ą¦Æą¦¾ą¦øą§ą¦Ŗą§‡ą¦•ą§ą¦Ÿ রেশিও ą¦Ŗą¦°ą¦æą¦¬ą¦°ą§ą¦¤ą¦Ø করুন"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¦•ą§ą¦Æą¦¾ą¦®ą§‡ą¦°ą¦¾ ą¦øą¦‚ą¦•ą§ą¦°ą¦¾ą¦Øą§ą¦¤ ą¦øą¦®ą¦øą§ą¦Æą¦¾?\nরিফিট করতে ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ করুন"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¦ą¦–ą¦Øą¦“ সমাধান হয়নি?\ną¦°ą¦æą¦­ą¦¾ą¦°ą§ą¦Ÿ করার ą¦œą¦Øą§ą¦Æ ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ করুন"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¦•ą§ą¦Æą¦¾ą¦®ą§‡ą¦°ą¦¾ ą¦øą¦‚ą¦•ą§ą¦°ą¦¾ą¦Øą§ą¦¤ ą¦øą¦®ą¦øą§ą¦Æą¦¾ নেই? বাতিল করতে ą¦Ÿą§ą¦Æą¦¾ą¦Ŗ ą¦•ą¦°ą§ą¦Øą„¤"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index b39b497f5c66..66e67b385cb8 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pomjerite gore desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pomjeri dolje lijevo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pomjerite dolje desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proÅ”irivanje oblačića <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sužavanje oblačića <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke aplikacije <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nemoj prikazivati razgovor u oblačićima"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Razumijem"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nedavni i odbačeni oblačići će se pojaviti ovdje"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatajte koristeći oblačiće"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi razgovori se pojavljuju kao ikone u donjem uglu ekrana. Dodirnite da ih proŔirite ili prevucite da ih odbacite."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljajte oblačićima u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da upravljate time koje aplikacije i razgovori mogu imati oblačić"</string>
<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="4564728020654658478">"Dodirnite da ponovo pokrenete ovu aplikaciju radi boljeg prikaza"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijenite format slike aplikacije u Postavkama"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijenite format slike"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index fe76e73135c9..62399ae07e7d 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mou a dalt a la dreta"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mou a baix a l\'esquerra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mou a baix a la dreta"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"desplega <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"replega <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuració de l\'aplicació <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora la bombolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostris la conversa com a bombolla"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entesos"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hi ha bombolles recents"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bombolles recents i les ignorades es mostraran aquĆ­"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Xateja amb bombolles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les converses noves es mostren com a icones en un extrem inferior de la pantalla. Toca per ampliar-les o arrossega per ignorar-les."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla les bombolles en qualsevol moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquĆ­ per gestionar quines aplicacions i converses poden fer servir bombolles"</string>
<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="4564728020654658478">"Toca per reiniciar aquesta aplicació i obtenir una millor visualització"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Canvia la relació d\'aspecte d\'aquesta aplicació a Configuració"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Canvia la relació d\'aspecte"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 70e29705806f..8e0aba0fdec6 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Přesunout vpravo nahoru"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Přesunout vlevo dolÅÆ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Přesunout vpravo dolÅÆ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozbalit <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sbalit <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"NastavenĆ­ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavřít bublinu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovat konverzaci v bublinÔch"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŽÔdné nedÔvné bubliny"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Zde se budou zobrazovat nedĆ”vnĆ© bubliny aĀ zavřenĆ© bubliny"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatujte pomocĆ­ bublin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"NovĆ© konverzace se zobrazĆ­ jako ikony vĀ dolnĆ­m rohu obrazovky. KlepnutĆ­m je rozbalĆ­te, přetaženĆ­m zavřete."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Nastavení bublin můžete kdykoli upravit"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"KlepnutĆ­m sem lze spravovat, kterĆ© aplikace aĀ konverzace mohou vytvÔřet bubliny"</string>
<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="4564728020654658478">"Klepnutím tuto aplikaci kvůli lepŔímu zobrazení restartujete"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Změnit vĀ NastavenĆ­ poměr stran tĆ©to aplikace"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Změnit poměr stran"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index c91cd7a956aa..d3989bcbd6f0 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flyt op til hĆøjre"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flyt ned til venstre"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flyt ned til hĆøjre"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"udvid <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen seneste bobler"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nye bobler og afviste bobler vises her"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat ved hjƦlp af bobler"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nye samtaler vises som ikoner nederst pƄ din skƦrm. Tryk for at udvide dem, eller trƦk for at lukke dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Administrer bobler nƄr som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryk her for at administrere, hvilke apps og samtaler der kan vises i bobler"</string>
<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="4564728020654658478">"Tryk for at genstarte denne app, sƄ visningen forbedres"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Skift denne apps billedformat i Indstillinger"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Skift billedformat"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 6ce475aa3c84..5d0ee2951e90 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -20,7 +20,7 @@
<string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string>
<string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
- <string name="pip_phone_enter_split" msgid="7042877263880641911">"ā€žGeteilter Bildschirmā€œ aktivieren"</string>
+ <string name="pip_phone_enter_split" msgid="7042877263880641911">"Splitscreen aktivieren"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü ā€žBild im Bildā€œ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
@@ -32,8 +32,8 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Größe anpassen"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"In Stash legen"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert bei geteiltem Bildschirm unter UmstƤnden nicht"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ā€žGeteilter Bildschirmā€œ wird in dieser App nicht unterstützt"</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"Die App funktioniert im Splitscreen-Modus unter UmstƤnden nicht"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Splitscreen wird in dieser App nicht unterstützt"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geƶffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundƤren Display mƶglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Nach rechts oben verschieben"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Nach unten links verschieben"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> maximieren"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> minimieren"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Einstellungen für <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Unterhaltung nicht als Bubble anzeigen"</string>
@@ -76,15 +78,21 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Keine kürzlich geschlossenen Bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Hier werden aktuelle und geschlossene Bubbles angezeigt"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bubbles zum Chatten verwenden"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Neue Unterhaltungen erscheinen als Symbole unten auf dem Display. Du kannst sie durch Antippen maximieren und durch Ziehen schließen."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubble-Einstellungen festlegen"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tippe hier, um zu verwalten, welche Apps und Unterhaltungen als Bubble angezeigt werden kƶnnen"</string>
<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="4564728020654658478">"Tippen, um diese App neu zu starten und die Ansicht zu verbessern"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"SeitenverhƤltnis der App in den Einstellungen Ƥndern"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"SeitenverhƤltnis Ƥndern"</string>
<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="449233070804658627">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
@@ -102,13 +110,12 @@
<string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
<string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Splitscreen"</string>
<string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
<string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
<string name="select_text" msgid="5139083974039906583">"AuswƤhlen"</string>
<string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
<string name="close_text" msgid="4986518933445178928">"Schließen"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
- <!-- no translation found for expand_menu_text (3847736164494181168) -->
- <skip />
+ <string name="expand_menu_text" msgid="3847736164494181168">"Menü öffnen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index ab5c44e9e07f..2b73528f7aef 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -66,20 +66,28 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ĪœĪµĻ„Ī±ĪŗĪÆĪ½Ī·ĻƒĪ· επάνω Γεξιά"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ĪœĪµĻ„Ī±ĪŗĪÆĪ½Ī·ĻƒĪ· κάτω Ī±ĻĪ¹ĻƒĻ„ĪµĻĪ¬"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ĪœĪµĻ„Ī±ĪŗĪÆĪ½Ī·ĻƒĪ· κάτω Γεξιά"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ανάπτυξη <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ĻƒĻĪ¼Ļ€Ļ„Ļ…Ī¾Ī· <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ī”Ļ…ĪøĪ¼ĪÆĻƒĪµĪ¹Ļ‚ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Παράβλ. για ĻƒĻ…Ī½Ī½ĪµĻ†."</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ĪĪ± μην γίνει προβολή της ĻƒĻ…Ī¶Ī®Ļ„Ī·ĻƒĪ·Ļ‚ σε ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±."</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Ī£Ļ…Ī¶Ī·Ļ„Ī®ĻƒĻ„Īµ Ļ‡ĻĪ·ĻƒĪ¹Ī¼ĪæĻ€ĪæĪ¹ĻŽĪ½Ļ„Ī±Ļ‚ ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±."</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Οι νέες ĻƒĻ…Ī¶Ī·Ļ„Ī®ĻƒĪµĪ¹Ļ‚ εμφανίζονται ως ĪŗĪ¹Ī½ĪæĻĪ¼ĪµĪ½Ī± εικονίΓια Ī® ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±. Ī Ī±Ļ„Ī®ĻƒĻ„Īµ για να ανοίξετε το ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹. Ī£ĻĻĪµĻ„Īµ για να το Ī¼ĪµĻ„Ī±ĪŗĪ¹Ī½Ī®ĻƒĪµĻ„Īµ."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Ελέγξτε τα ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± ανά Ļ€Ī¬ĻƒĪ± ĻƒĻ„Ī¹Ī³Ī¼Ī®."</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Ī Ī±Ļ„Ī®ĻƒĻ„Īµ Ī”Ī¹Ī±Ļ‡ĪµĪÆĻĪ¹ĻƒĪ· για να Ī±Ļ€ĪµĪ½ĪµĻĪ³ĪæĻ€ĪæĪ¹Ī®ĻƒĪµĻ„Īµ τα ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± Ī±Ļ€ĻŒ αυτήν την εφαρμογή."</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Ī Ī±Ļ„Ī®ĻƒĻ„Īµ Ī”Ī¹Ī±Ļ‡ĪµĪÆĻĪ¹ĻƒĪ· για να Ī±Ļ€ĪµĪ½ĪµĻĪ³ĪæĻ€ĪæĪ¹Ī®ĻƒĪµĻ„Īµ τα ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± Ī±Ļ€ĻŒ αυτή την εφαρμογή."</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Το κατάλαβα"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Δεν υπάρχουν Ļ€ĻĻŒĻƒĻ†Ī±Ļ„Ī± ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Τα Ļ€ĻĻŒĻƒĻ†Ī±Ļ„Ī± ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± και τα ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± που Ļ€Ī±ĻĪ±Ī²Ī»Ī­ĻˆĪ±Ļ„Īµ θα εμφανίζονται ĪµĪ“ĻŽ."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Ī£Ļ…Ī¶Ī·Ļ„Ī®ĻƒĻ„Īµ Ļ‡ĻĪ·ĻƒĪ¹Ī¼ĪæĻ€ĪæĪ¹ĻŽĪ½Ļ„Ī±Ļ‚ ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Οι νέες ĻƒĻ…Ī¶Ī·Ļ„Ī®ĻƒĪµĪ¹Ļ‚ εμφανίζονται ως εικονίΓια σε μια Ī±Ļ€ĻŒ τις κάτω γωνίες της ĪæĪøĻŒĪ½Ī·Ļ‚. Ī Ī±Ļ„Ī®ĻƒĻ„Īµ για να τις Ī±Ī½Ī±Ļ€Ļ„ĻĪ¾ĪµĻ„Īµ Ī® ĻƒĻĻĪµĻ„Īµ για να τις Ļ€Ī±ĻĪ±Ī²Ī»Ī­ĻˆĪµĻ„Īµ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ελέγξτε τα ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī± ανά Ļ€Ī¬ĻƒĪ± ĻƒĻ„Ī¹Ī³Ī¼Ī®."</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ī Ī±Ļ„Ī®ĻƒĻ„Īµ ĪµĪ“ĻŽ για τη Ī“Ī¹Ī±Ļ‡ĪµĪÆĻĪ¹ĻƒĪ· ĪµĻ†Ī±ĻĪ¼ĪæĪ³ĻŽĪ½ και ĻƒĻ…Ī¶Ī·Ļ„Ī®ĻƒĪµĻ‰Ī½ που προβάλλουν ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹Ī±"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Ī”Ī¹Ī±Ļ‡ĪµĪÆĻĪ¹ĻƒĪ·"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το ĻƒĻ…Ī½Ī½ĪµĻ†Ī¬ĪŗĪ¹ παραβλέφθηκε."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Ī Ī±Ļ„Ī®ĻƒĻ„Īµ για να ĪµĻ€Ī±Ī½ĪµĪŗĪŗĪ¹Ī½Ī®ĻƒĪµĻ„Īµ αυτή την εφαρμογή για ĪŗĪ±Ī»ĻĻ„ĪµĻĪ· προβολή."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Ī Ī±Ļ„Ī®ĻƒĻ„Īµ για να ĪµĻ€Ī±Ī½ĪµĪŗĪŗĪ¹Ī½Ī®ĻƒĪµĻ„Īµ αυτή την εφαρμογή για ĪŗĪ±Ī»ĻĻ„ĪµĻĪ· προβολή"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Αλλάξτε τον λόγο Ī“Ī¹Ī±ĻƒĻ„Ī¬ĻƒĪµĻ‰Ī½ αυτής της εφαρμογής ĻƒĻ„Ī¹Ļ‚ Ī”Ļ…ĪøĪ¼ĪÆĻƒĪµĪ¹Ļ‚"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Αλλαγή Ī»ĻŒĪ³ĪæĻ… Ī“Ī¹Ī±ĻƒĻ„Ī¬ĻƒĪµĻ‰Ī½"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nĪ Ī±Ļ„Ī®ĻƒĻ„Īµ για ĪµĻ€Ī¹Ī“Ī¹ĻŒĻĪøĻ‰ĻƒĪ·."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν Ī“Ī¹ĪæĻĪøĻŽĪøĪ·ĪŗĪµ;\nĪ Ī±Ļ„Ī®ĻƒĻ„Īµ για επαναφορά."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Ī Ī±Ļ„Ī®ĻƒĻ„Īµ για Ļ€Ī±ĻĪ¬Ī²Ī»ĪµĻˆĪ·."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index ea91014298df..2f0e898d5db7 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 01bdf95da918..a338905fa299 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles anytime"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index ea91014298df..2f0e898d5db7 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index ea91014298df..2f0e898d5db7 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Move top right"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Move bottom left"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expand <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"collapse <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
@@ -76,10 +78,16 @@
<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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat using bubbles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Control bubbles at any time"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tap here to manage which apps and conversations can bubble"</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="4564728020654658478">"Tap to restart this app for a better view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Change this app\'s aspect ratio in Settings"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Change aspect ratio"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index f6dac79d0379..20344389ddcc 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ŽMove top rightā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€ā€ā€ā€Žā€Žā€ā€ā€Žā€Žā€ā€ā€Žā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€ā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€ā€ā€ā€ā€ā€ā€ā€Žā€Žā€Žā€ā€Žā€ŽMove bottom leftā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€Žā€ā€ā€ā€ā€Žā€ā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€ā€ā€ā€ā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€Žā€Žā€Žā€Žā€ŽMove bottom rightā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€Žā€Žā€Žā€Žā€Žā€Žexpand ā€Žā€ā€Žā€Žā€ā€ā€Ž<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€Žā€Žā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€ā€ā€Žā€ā€Žā€ā€ā€Žā€ā€Žā€Žā€ā€ā€Žā€Žā€ā€ā€Žā€Žā€ā€ā€Žā€Žā€Žā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žcollapse ā€Žā€ā€Žā€Žā€ā€ā€Ž<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€ā€Žā€ā€ā€ā€ā€Žā€ā€ā€Žā€ā€ā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€ā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€ā€Ž<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>ā€Žā€ā€Žā€Žā€ā€ā€ā€Ž settingsā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€ā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€ā€Žā€ā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€ā€Žā€ā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€ŽDismiss bubbleā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€ā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€ā€ā€ā€Žā€ā€Žā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€ŽDon’t bubble conversationā€Žā€ā€Žā€Žā€ā€Ž"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ā€ā€ā€Žā€ā€ā€ā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€Žā€ā€ā€ā€ā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€ŽGot itā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€ā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€ā€Žā€Žā€Žā€ŽNo recent bubblesā€Žā€ā€Žā€Žā€ā€Ž"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€ā€ā€Žā€Žā€ā€ā€ā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€ā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€ā€ā€ā€ā€ā€Žā€ā€ā€ā€ā€Žā€Žā€Žā€ā€ŽRecent bubbles and dismissed bubbles will appear hereā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€ā€ā€ā€Žā€ā€ā€ā€Žā€ā€ŽChat using bubblesā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€Žā€ŽNew conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.ā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€ā€Žā€ā€Žā€Žā€ŽControl bubbles anytimeā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€ā€ā€ā€ā€Žā€ā€ā€ā€Žā€ā€ā€Žā€ā€ā€Žā€ā€ā€ā€ā€ā€Žā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€ā€Žā€Žā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€ā€ā€Žā€ā€ā€ā€Žā€ā€ā€Žā€ŽTap here to manage which apps and conversations can bubbleā€Žā€ā€Žā€Žā€ā€Ž"</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="4564728020654658478">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€ā€ā€ā€ā€Žā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ŽTap to restart this app for a better viewā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€Žā€Žā€ā€ā€Žā€Žā€ā€Žā€ā€Žā€ā€Žā€ā€Žā€Žā€ā€Žā€ā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€ā€ā€Žā€ā€Žā€ā€ā€Žā€Žā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€ā€ā€Žā€Žā€Žā€ŽChange this app\'s aspect ratio in Settingsā€Žā€ā€Žā€Žā€ā€Ž"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ā€Žā€ā€Žā€Žā€Žā€Žā€Žā€ā€Žā€ā€ā€ā€Žā€Žā€Žā€Žā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€Žā€ā€ā€ā€Žā€ā€ā€ā€ā€ā€Žā€Žā€Žā€ā€ā€ā€ā€ā€ā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€Žā€Žā€ā€Žā€ā€Žā€ā€ā€ā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€Žā€Žā€ā€ā€Žā€Žā€ā€Žā€Žā€ŽChange aspect ratioā€Žā€ā€Žā€Žā€ā€Ž"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 0190ea85a30b..ebf67f6e164e 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Ubicar arriba a la derecha"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Ubicar abajo a la izquierda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ubicar abajo a la derecha"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expandir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Descartar burbuja"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbuja"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las que se descartaron aparecerƔn aquƭ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat con burbujas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Las conversaciones nuevas aparecen como Ć­conos en la esquina inferior de la pantalla. Presiona para expandirlas, o bien arrastra para descartarlas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Presiona para administrar las apps y conversaciones que pueden mostrar burbujas"</string>
<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="4564728020654658478">"Presiona para reiniciar esta app y tener una mejor vista"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta app en Configuración"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index ea44bead6766..05171b2fc6dd 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover arriba a la derecha"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover abajo a la izquierda."</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abajo a la derecha"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"desplegar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ajustes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No hay burbujas recientes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Las burbujas recientes y las cerradas aparecerƔn aquƭ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatea con burbujas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Las nuevas conversaciones aparecen como iconos en una esquina inferior de la pantalla. Tócalas para ampliarlas o arrÔstralas para cerrarlas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controla las burbujas cuando quieras"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca aquƭ para gestionar quƩ aplicaciones y conversaciones pueden usar burbujas"</string>
<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="4564728020654658478">"Toca para reiniciar esta aplicación y obtener una mejor vista"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambiar la relación de aspecto de esta aplicación en Ajustes"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar relación de aspecto"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 90feff32cc2b..3af3c16ede24 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Teisalda üles paremale"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Teisalda alla vasakule"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Teisalda alla paremale"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"laienda <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ahenda <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Rakenduse <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> seaded"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Sule mull"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ƅra kuva vestlust mullina"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Selge"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hiljutisi mulle pole"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Siin kuvatakse hiljutised ja suletud mullid."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Vestelge mullide abil"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Uued vestlused kuvatakse ekraanikuva alanurgas ikoonidena. Puudutage nende laiendamiseks vƵi lohistage neist loobumiseks."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Juhtige mulle igal ajal"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Puudutage siin, et hallata, milliseid rakendusi ja vestlusi saab mullina kuvada"</string>
<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="4564728020654658478">"Puudutage, et see rakendus parema vaate jaoks taaskƤivitada"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muutke selle rakenduse kuvasuhet jaotises Seaded"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Muutke kuvasuhet"</string>
<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>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskƤivitada parema vaate saavutamiseks?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskƤivitada, et see nƤeks ekraanikuval parem vƤlja, kuid vƵite kaotada edenemise vƵi salvestamata muudatused"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskƤivitada, et see nƤeks ekraanikuval parem vƤlja, kuid vƵite kaotada edenemise vƵi salvestamata muudatused."</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"TaaskƤivita"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ƅra kuva uuesti"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index de27a8045e85..1dbc57b73f6c 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -36,7 +36,7 @@
<string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Aplikazioak ez du onartzen pantaila zatitua"</string>
<string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
- <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
+ <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da exekutatu bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string>
<string name="divider_title" msgid="1963391955593749442">"Pantaila-zatitzailea"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Eraman goialdera, eskuinetara"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Eraman behealdera, ezkerretara"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Eraman behealdera, eskuinetara"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"zabaldu <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"tolestu <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aplikazioaren ezarpenak"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baztertu burbuila"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ez erakutsi elkarrizketak burbuila gisa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ados"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ez dago azkenaldiko burbuilarik"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Azken burbuilak eta baztertutakoak agertuko dira hemen"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Txateatu burbuilak erabilita"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Elkarrizketa berriak ikono gisa agertzen dira pantailaren beheko izkinan. Zabaltzeko, saka itzazu. Baztertzeko, aldiz, arrasta itzazu."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolatu burbuilak edonoiz"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Sakatu hau burbuiletan zein aplikazio eta elkarrizketa ager daitezkeen kudeatzeko"</string>
<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="4564728020654658478">"Hobeto ikusteko, sakatu hau, eta aplikazioa berrabiarazi egingo da"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Aldatu aplikazioaren aspektu-erlazioa ezarpenetan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Aldatu aspektu-erlazioa"</string>
<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>
@@ -102,7 +110,7 @@
<string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
<string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitzea"</string>
<string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
<string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
<string name="select_text" msgid="5139083974039906583">"Hautatu"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 13a2ea2db140..f7547602dcea 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -59,13 +59,15 @@
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج Ų§Ų² «حالت ŪŒŚ©ā€ŒŲÆŲ³ŲŖŪŒĀ»"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"ŲŖŁ†ŲøŪŒŁ…Ų§ŲŖ برای Ų­ŲØŲ§ŲØŚ©ā€ŒŁ‡Ų§ŪŒ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
- <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Ų§ŁŲ²ŁˆŲÆŁ† برگؓت به پؓته"</string>
+ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Ų§ŁŲ²ŁˆŲÆŁ† برگؓتن به پؓته"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> Ų§Ų² <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> Ų§Ų² <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> Ł…ŁˆŲ±ŲÆ بیؓتر"</string>
<string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"انتقال به بالا سمت Ų±Ų§Ų³ŲŖ"</string>
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"انتقال به بالا سمت چپ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"انتقال به Ł¾Ų§ŪŒŪŒŁ† سمت Ų±Ų§Ų³ŲŖ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"انتقال به Ł¾Ų§ŪŒŪŒŁ† سمت چپ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ازهم ŲØŲ§Ų² کردن <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"جمع کردن <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ŲŖŁ†ŲøŪŒŁ…Ų§ŲŖ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ų±ŲÆ کردن Ų­ŲØŲ§ŲØŚ©"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"مکالمه ŲÆŲ± Ų­ŲØŲ§ŲØ نؓان داده Ł†Ų“ŁˆŲÆ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ł…ŲŖŁˆŲ¬Ł‡ā€ŒŲ§Ł…"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ł‡ŪŒŚ† Ų­ŲØŲ§ŲØŚ© جدیدی وجود ندارد"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ų­ŲØŲ§ŲØŚ©ā€ŒŁ‡Ų§ŪŒ اخیر و Ų­ŲØŲ§ŲØŚ©ā€ŒŁ‡Ų§ŪŒ ردؓده Ų§ŪŒŁ†Ų¬Ų§ ظاهر Ų®ŁˆŲ§Ł‡Ł†ŲÆ Ų“ŲÆ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"گپ زدن بااستفاده Ų§Ų² Ų­ŲØŲ§ŲØŚ©"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Ł…Ś©Ų§Ł„Ł…Ł‡ā€ŒŁ‡Ų§ŪŒ جدید ŲØŁ‡ā€ŒŲµŁˆŲ±ŲŖ نماد ŲÆŲ± ŚÆŁˆŲ“Ł‡ Ł¾Ų§ŪŒŪŒŁ† ŲµŁŲ­Ł‡ā€ŒŁ†Ł…Ų§ŪŒŲ“ نؓان داده Ł…ŪŒā€ŒŲ“ŁˆŲÆ. برای ازهم بازکردن Ų¢Ł†ā€ŒŁ‡Ų§ ضربه ŲØŲ²Ł†ŪŒŲÆ یا برای ŲØŲ³ŲŖŁ†ŲŒ Ų¢Ł†ā€ŒŁ‡Ų§ Ų±Ų§ بکؓید."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کنترل Ų­ŲØŲ§ŲØŚ©ā€ŒŁ‡Ų§ ŲÆŲ± Ł‡Ų±Ų²Ł…Ų§Ł†ŪŒ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"برای Ł…ŲÆŪŒŲ±ŪŒŲŖ Ų§ŪŒŁ†Ś©Ł‡ کدام ŲØŲ±Ł†Ų§Ł…Ł‡ā€ŒŁ‡Ų§ و Ł…Ś©Ų§Ł„Ł…Ł‡ā€ŒŁ‡Ų§ Ų­ŲØŲ§ŲØ داؓته ŲØŲ§Ų“Ł†ŲÆŲŒ ضربه ŲØŲ²Ł†ŪŒŲÆ"</string>
<string name="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="4564728020654658478">"برای داؓتن Ł†Ł…Ų§ŪŒŪŒ ŲØŁ‡ŲŖŲ±ŲŒ ضربه ŲØŲ²Ł†ŪŒŲÆ ŲŖŲ§ Ų§ŪŒŁ† برنامه ŲØŲ§Ų²Ų±Ų§Ł‡ā€ŒŲ§Ł†ŲÆŲ§Ų²ŪŒ ؓود"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"نسبت ابعادی Ų§ŪŒŁ† برنامه Ų±Ų§ ŲÆŲ± Ā«ŲŖŁ†ŲøŪŒŁ…Ų§ŲŖĀ» تغییر ŲÆŁ‡ŪŒŲÆ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغییر نسبت ابعادی"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŲÆŁˆŲ±ŲØŪŒŁ† مؓکل دارد؟\nبرای ŲŖŁ†ŲøŪŒŁ… Ł…Ų¬ŲÆŲÆ اندازه ضربه ŲØŲ²Ł†ŪŒŲÆ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مؓکل برطرف Ł†Ų“ŲÆŲŸ\nبرای برگرداندن ضربه ŲØŲ²Ł†ŪŒŲÆ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŲÆŁˆŲ±ŲØŪŒŁ† Ł…Ų“Ś©Ł„ŪŒ Ł†ŲÆŲ§Ų±ŲÆŲŸ برای بستن ضربه ŲØŲ²Ł†ŪŒŲÆ."</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 92fa76013134..9094c7335571 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"SiirrƤ oikeaan ylƤreunaan"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"SiirrƤ vasempaan alareunaan"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"SiirrƤ oikeaan alareunaan"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"laajenna <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"tiivistƤ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: asetukset"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ohita kupla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ƄlƤ nƤytƤ kuplia keskusteluista"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Okei"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ei viimeaikaisia kuplia"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viimeaikaiset ja ƤskettƤin ohitetut kuplat nƤkyvƤt tƤƤllƤ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chattaile kuplien avulla"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Uudet keskustelut nƤkyvƤt kuvakkeina nƤytƶn alareunassa. Laajenna ne napauttamalla tai hylkƤƤ ne vetƤmƤllƤ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Muuta kuplien asetuksia milloin tahansa"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Valitse napauttamalla tƤstƤ, mitkƤ sovellukset ja keskustelut voivat kuplia"</string>
<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="4564728020654658478">"Napauta, niin sovellus kƤynnistyy uudelleen paremmin nƤytƶlle sopivana"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Muuta tƤmƤn sovelluksen kuvasuhdetta Asetuksissa"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Vaihda kuvasuhdetta"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 7814b7d38fed..b26c1b4e3018 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"DƩplacer dans coin sup. droit"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"DƩplacer dans coin inf. gauche"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"DƩplacer dans coin inf. droit"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"dƩvelopper <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"rƩduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ParamĆØtres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle rƩcente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles rƩcentes et les bulles ignorƩes s\'afficheront ici"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Clavarder en utilisant des bulles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les nouvelles conversations apparaissent sous forme d\'icƓnes dans le coin infƩrieur de votre Ʃcran. Touchez-les icƓnes pour dƩvelopper les conversations ou faites-les glisser pour les supprimer."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"GƩrez les bulles en tout temps"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Touchez ici pour gƩrer les applis et les conversations Ơ inclure aux bulles"</string>
<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="4564728020654658478">"Touchez pour redƩmarrer cette application afin d\'obtenir un meilleur affichage"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramĆØtres"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problĆØme d\'appareil photo? Touchez pour ignorer."</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index da5b5c9bfeba..6e6420a22f50 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"DƩplacer en haut Ơ droite"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"DƩplacer en bas Ơ gauche"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"DƩplacer en bas Ơ droite"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"DƩvelopper <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"RƩduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ParamĆØtres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle rƩcente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles rƩcentes et ignorƩes s\'afficheront ici"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatter via des bulles"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Les nouvelles conversations apparaissent sous forme d\'icƓnes dans l\'un des coins infƩrieurs de votre Ʃcran. Appuyez dessus pour les dƩvelopper ou faites-les glisser pour les supprimer."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ContrƓlez les bulles Ơ tout moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Appuyez ici pour gƩrer les applis et conversations s\'affichant dans des bulles"</string>
<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="4564728020654658478">"Appuyez pour redƩmarrer cette appli et obtenir une meilleure vue."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Modifiez le format de cette appli dans les ParamĆØtres."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier le format"</string>
<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>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"DƩveloppez pour obtenir plus d\'informations"</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redƩmarrer l\'appli pour en amƩliorer son aspect sur votre Ʃcran, mais vous risquez de perdre votre progression ou les modifications non enregistrƩes"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redƩmarrer l\'appli pour un meilleur rendu sur votre Ʃcran, mais il se peut que vous perdiez votre progression ou les modifications non enregistrƩes"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"RedƩmarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index c08cff86f63c..bf3a45b46484 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover Ć” parte superior dereita"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover Ć” parte infer. esquerda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover Ć” parte inferior dereita"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"despregar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"contraer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar burbulla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mostrar a conversa como burbulla"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Entendido"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Non hai burbullas recentes"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"As burbullas recentes e ignoradas aparecerƔn aquƭ."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatea usando burbullas"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"As conversas novas móstranse como iconas nunha das esquinas inferiores da pantalla. Tócaas para amplialas ou arrÔstraas para pechalas."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlar as burbullas"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toca para xestionar as aplicacións e conversas que poden aparecer en burbullas"</string>
<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="4564728020654658478">"Toca o botón para reiniciar esta aplicación e gozar dunha mellor visualización"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia a proporción desta aplicación en Configuración"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambiar a proporción"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 2a521991ef8d..84c818230621 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ઉપર ąŖœąŖ®ąŖ£ą«‡ ખસેઔો"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ąŖØą«€ąŖšą«‡ ઔાબે ખસેઔો"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ąŖØą«€ąŖšą«‡ ąŖœąŖ®ąŖ£ą«‡ ખસેઔો"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ąŖ®ą«‹ąŖŸą«ąŖ‚ ąŖ•ąŖ°ą«‹"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> નાનું ąŖ•ąŖ°ą«‹"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ąŖøą«‡ąŖŸąŖæąŖ‚ąŖ—"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોઔી દો"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ąŖµąŖ¾ąŖ¤ąŖšą«€ąŖ¤ąŖØą«‡ બબલ ąŖ•ąŖ°ąŖ¶ą«‹ નહીં"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"સમજાઈ ગયું"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ąŖ¤ąŖ¾ąŖœą«‡ąŖ¤ąŖ°ąŖØąŖ¾ ąŖ•ą«‹ąŖˆ બબલ ąŖØąŖ„ą«€"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ąŖąŖ•ąŖ¦ąŖ® નવા બબલ અને છોઔી દીધેલા બબલ અહીં દેખાશે"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"બબલનો ઉપયોગ કરીને ąŖšą«…ąŖŸ ąŖ•ąŖ°ą«‹"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"નવી ąŖµąŖ¾ąŖ¤ąŖšą«€ąŖ¤ą«‹ તમારી ąŖøą«ąŖ•ą«ąŖ°ą«€ąŖØąŖØąŖ¾ ąŖØą«€ąŖšą«‡ąŖØąŖ¾ ખૂણામાં આઇકન તરીકે દેખાય છે. તેમને ąŖ®ą«‹ąŖŸą«€ કરવા ąŖ®ąŖ¾ąŖŸą«‡, તેમના ąŖŖąŖ° ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹ અ઄વા તેમને છોઔી દેવા ąŖ®ąŖ¾ąŖŸą«‡, તેમને ąŖ–ą«‡ąŖ‚ąŖšą«‹."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"બબલને ąŖ•ą«‹ąŖˆąŖŖąŖ£ સમયે ąŖØąŖæąŖÆąŖ‚ąŖ¤ą«ąŖ°ąŖæąŖ¤ કરે છે"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ąŖ•ąŖˆ ąŖąŖŖ અને ąŖµąŖ¾ąŖ¤ąŖšą«€ąŖ¤ą«‹ąŖØą«‡ બબલ કરવા માગો છો તે ąŖ®ą«‡ąŖØą«‡ąŖœ કરવા ąŖ®ąŖ¾ąŖŸą«‡, અહીં ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹"</string>
<string name="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="4564728020654658478">"વધુ સારા ąŖµą«ąŖÆą«‚ ąŖ®ąŖ¾ąŖŸą«‡, ąŖ† ąŖąŖŖąŖØą«‡ ąŖ«ąŖ°ą«€ ąŖ¶ąŖ°ą«‚ કરવા ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ąŖøą«‡ąŖŸąŖæąŖ‚ąŖ—ąŖ®ąŖ¾ąŖ‚ ąŖ† ąŖąŖŖąŖØą«‹ ąŖøąŖ¾ąŖŖą«‡ąŖ•ą«ąŖ· ąŖ—ą«ąŖ£ą«‹ąŖ¤ą«ąŖ¤ąŖ° બદલો"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ąŖøąŖ¾ąŖŖą«‡ąŖ•ą«ąŖ· ąŖ—ą«ąŖ£ą«‹ąŖ¤ą«ąŖ¤ąŖ° બદલો"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં ąŖøąŖ®ąŖøą«ąŖÆąŖ¾ąŖ“ છે?\nફરી઄ી ફિટ કરવા ąŖ®ąŖ¾ąŖŸą«‡ ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો ąŖØąŖ„ą«€ ąŖ„ąŖÆą«‹?\nપહેલાંના ąŖŖąŖ° પાછું ફેરવવા ąŖ®ąŖ¾ąŖŸą«‡ ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં ąŖ•ą«‹ąŖˆ ąŖøąŖ®ąŖøą«ąŖÆąŖ¾ ąŖØąŖ„ą«€? છોઔી દેવા ąŖ®ąŖ¾ąŖŸą«‡ ąŖŸą«…ąŖŖ ąŖ•ąŖ°ą«‹."</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index fb5040b36a89..8068f50d9cf6 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ą¤øą¤¬ą¤øą„‡ ऊपर ą¤¦ą¤¾ą¤ˆą¤‚ ओर ą¤²ą„‡ ą¤œą¤¾ą¤ą¤‚"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¤¬ą¤¾ą¤ˆą¤‚ ओर ą¤øą¤¬ą¤øą„‡ ą¤Øą„€ą¤šą„‡ ą¤²ą„‡ ą¤œą¤¾ą¤ą¤‚"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą¤øą¤¬ą¤øą„‡ ą¤Øą„€ą¤šą„‡ ą¤¦ą¤¾ą¤ˆą¤‚ ओर ą¤²ą„‡ ą¤œą¤¾ą¤ą¤‚"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤•ą„‹ बऔ़ा ą¤•ą¤°ą„‡ą¤‚"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤•ą„‹ ą¤›ą„‹ą¤Ÿą¤¾ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą¤•ą„€ ą¤øą„‡ą¤Ÿą¤æą¤‚ą¤—"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल ą¤–ą¤¾ą¤°ą¤æą¤œ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą¤¬ą¤¾ą¤¤ą¤šą„€ą¤¤ ą¤•ą„‹ बबल न ą¤•ą¤°ą„‡ą¤‚"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¤ ą„€ą¤• ą¤¹ą„ˆ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ą¤¹ą„€ ą¤•ą„‡ ą¤•ą„‹ą¤ˆ ą¤¬ą¤¬ą¤²ą„ą¤ø ą¤Øą¤¹ą„€ą¤‚ ą¤¹ą„ˆą¤‚"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ą¤¹ą„€ ą¤•ą„‡ ą¤¬ą¤¬ą¤²ą„ą¤ø और ą¤¹ą¤Ÿą¤¾ą¤ ą¤—ą¤ ą¤¬ą¤¬ą¤²ą„ą¤ø यहां ą¤¦ą¤æą¤–ą„‡ą¤‚ą¤—ą„‡"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą¤¬ą¤¬ą¤²ą„ą¤ø का ą¤‡ą¤øą„ą¤¤ą„‡ą¤®ą¤¾ą¤² ą¤•ą¤°ą¤•ą„‡ ą¤šą„ˆą¤Ÿ ą¤•ą¤°ą„‡ą¤‚"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नई ą¤¬ą¤¾ą¤¤ą¤šą„€ą¤¤, ą¤†ą¤Ŗą¤•ą„€ ą¤øą„ą¤•ą„ą¤°ą„€ą¤Ø पर ą¤øą¤¬ą¤øą„‡ ą¤Øą„€ą¤šą„‡ ą¤†ą¤‡ą¤•ą„‰ą¤Ø ą¤•ą„‡ ą¤¤ą„Œą¤° पर ą¤¦ą¤æą¤–ą¤¤ą„€ ą¤¹ą„ˆą¤‚. ą¤•ą¤æą¤øą„€ ą¤†ą¤‡ą¤•ą„‰ą¤Ø ą¤•ą„‹ बऔ़ा ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ उस पर ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą„‡ą¤‚ या ą¤–ą¤¾ą¤°ą¤æą¤œ ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤‰ą¤øą„‡ ą¤–ą„€ą¤‚ą¤šą„‡ą¤‚ और ą¤›ą„‹ą¤”ą¤¼ą„‡ą¤‚."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब ą¤šą¤¾ą¤¹ą„‡ą¤‚, ą¤¬ą¤¬ą¤²ą„ą¤ø ą¤•ą„€ ą¤øą„ą¤µą¤æą¤§ą¤¾ ą¤•ą„‹ ą¤•ą¤‚ą¤Ÿą„ą¤°ą„‹ą¤² ą¤•ą¤°ą„‡ą¤‚"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą¤•ą¤æą¤øą„€ ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø और ą¤¬ą¤¾ą¤¤ą¤šą„€ą¤¤ ą¤•ą„‡ ą¤²ą¤æą¤ बबल ą¤•ą„€ ą¤øą„ą¤µą¤æą¤§ą¤¾ ą¤•ą„‹ ą¤®ą„ˆą¤Øą„‡ą¤œ ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ यहां ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="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="4564728020654658478">"ą¤¬ą„‡ą¤¹ą¤¤ą¤° ą¤µą„ą¤Æą„‚ ą¤Ŗą¤¾ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤, ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą¤•ą„‡ ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø ą¤•ą„‹ ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą„‡ą¤‚"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¤øą„‡ą¤Ÿą¤æą¤‚ą¤— ą¤®ą„‡ą¤‚ ą¤œą¤¾ą¤•ą¤° इस ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø का ą¤†ą¤øą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤¶ą¤æą¤Æą„‹ (ą¤²ą¤‚ą¤¬ą¤¾ą¤ˆ-ą¤šą„Œą¤”ą¤¼ą¤¾ą¤ˆ का ą¤…ą¤Øą„ą¤Ŗą¤¾ą¤¤) ą¤¬ą¤¦ą¤²ą„‡ą¤‚"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¤†ą¤øą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤¶ą¤æą¤Æą„‹ (ą¤²ą¤‚ą¤¬ą¤¾ą¤ˆ-ą¤šą„Œą¤”ą¤¼ą¤¾ą¤ˆ का ą¤…ą¤Øą„ą¤Ŗą¤¾ą¤¤) ą¤¬ą¤¦ą¤²ą„‡ą¤‚"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¤•ą„ą¤Æą¤¾ ą¤•ą„ˆą¤®ą¤°ą„‡ ą¤øą„‡ ą¤œą„ą¤”ą¤¼ą„€ ą¤•ą„‹ą¤ˆ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤¹ą„ˆ?\nफिर ą¤øą„‡ फ़िट ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¤•ą„ą¤Æą¤¾ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤ ą„€ą¤• ą¤Øą¤¹ą„€ą¤‚ ą¤¹ą„ą¤ˆ?\ną¤Ŗą¤¹ą¤²ą„‡ ą¤œą„ˆą¤øą¤¾ ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¤•ą„ą¤Æą¤¾ ą¤•ą„ˆą¤®ą¤°ą„‡ ą¤øą„‡ ą¤œą„ą¤”ą¤¼ą„€ ą¤•ą„‹ą¤ˆ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤Øą¤¹ą„€ą¤‚ ą¤¹ą„ˆ? ą¤–ą¤¾ą¤°ą¤æą¤œ ą¤•ą¤°ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤Ÿą„ˆą¤Ŗ ą¤•ą¤°ą„‡ą¤‚."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ą¤ ą„€ą¤• ą¤¹ą„ˆ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ą¤œą¤¼ą„ą¤Æą¤¾ą¤¦ą¤¾ ą¤œą¤¾ą¤Øą¤•ą¤¾ą¤°ą„€ ą¤•ą„‡ ą¤²ą¤æą¤ बऔ़ा ą¤•ą¤°ą„‡ą¤‚."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ą¤¬ą„‡ą¤¹ą¤¤ą¤° ą¤µą„ą¤Æą„‚ ą¤Ŗą¤¾ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø ą¤•ą„‹ ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ करना ą¤¹ą„ˆ?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ą¤øą„ą¤•ą„ą¤°ą„€ą¤Ø पर ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø का ą¤¬ą„‡ą¤¹ą¤¤ą¤° ą¤µą„ą¤Æą„‚ ą¤Ŗą¤¾ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤‰ą¤øą„‡ ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą„‡ą¤‚. हालांकि, ą¤†ą¤Ŗą¤Øą„‡ ą¤œą„‹ बदलाव ą¤øą„‡ą¤µ ą¤Øą¤¹ą„€ą¤‚ ą¤•ą¤æą¤ ą¤¹ą„ˆą¤‚ या अब तक ą¤œą„‹ काम ą¤•ą¤æą¤ ą¤¹ą„ˆą¤‚ उनका ą¤”ą„‡ą¤Ÿą¤¾, ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą¤Øą„‡ पर मिट सकता ą¤¹ą„ˆ"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ą¤øą„ą¤•ą„ą¤°ą„€ą¤Ø पर ą¤ą¤Ŗą„ą¤²ą¤æą¤•ą„‡ą¤¶ą¤Ø का ą¤¬ą„‡ą¤¹ą¤¤ą¤° ą¤µą„ą¤Æą„‚ ą¤Ŗą¤¾ą¤Øą„‡ ą¤•ą„‡ ą¤²ą¤æą¤ ą¤‰ą¤øą„‡ ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą„‡ą¤‚. हालांकि, ą¤‡ą¤øą¤øą„‡ अब तक किया गया काम और ą¤øą„‡ą¤µ न ą¤•ą¤æą¤ ą¤—ą¤ बदलाव मिट ą¤øą¤•ą¤¤ą„‡ ą¤¹ą„ˆą¤‚"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"ą¤°ą¤¦ą„ą¤¦ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą„‡ą¤‚"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर ą¤øą„‡ न ą¤¦ą¤æą¤–ą¤¾ą¤ą¤‚"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 2535657c7d86..5f0b866ee4de 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premjesti u gornji desni kut"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premjesti u donji lijevi kut"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premjestite u donji desni kut"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"proÅ”irite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sažmite oblačić <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Zaustavi razgovor u oblačićima"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Shvaćam"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ovdje će se prikazivati nedavni i odbačeni oblačići"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Oblačići u chatu"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi se razgovori prikazuju kao ikone u donjem kutu zaslona. Dodirnite da biste ih proŔirili ili ih povucite da biste ih odbacili."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačićima u svakom trenutku"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da biste odredili koje aplikacije i razgovori mogu imati oblačić"</string>
<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="4564728020654658478">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Promijeni omjer slike ove aplikacije u postavkama"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Promijeni omjer slike"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 7566439ec7f8..d89e2923b5fe 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ƁthelyezƩs fel Ʃs jobbra"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ƁthelyezƩs le Ʃs balra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ƁthelyezƩs le Ʃs jobbra"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> kibontƔsa"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ƶsszecsukƔsa"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> beƔllƭtƔsai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"BuborƩk elvetƩse"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne jelenjen meg a beszƩlgetƩs buborƩkban"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ɖrtem"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nincsenek buborékok a közelmúltból"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"A legutóbbi és az elvetett buborékok itt jelennek majd meg"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Buborékokat hasznÔló csevegés"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Az Ćŗj beszĆ©lgetĆ©sek ikonokkĆ©nt jelennek meg a kĆ©pernyő alsó sarkĆ”ban. KoppintĆ”ssal kibonthatja, hĆŗzĆ”ssal pedig elvetheti őket."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"BuborƩkok vezƩrlƩse bƔrmikor"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ide koppintva jelenĆ­thetők meg az alkalmazĆ”sok Ć©s a beszĆ©lgetĆ©sek buborĆ©kkĆ©nt"</string>
<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="4564728020654658478">"A jobb nézet érdekében koppintson az alkalmazÔs újraindítÔsÔhoz."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Az app méretarÔnyÔt a BeÔllítÔsokban módosíthatja"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"MéretarÔny módosítÔsa"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 2b20870b1048..9c517b29d291 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ÕÕ„Õ²Õ”ÖƒÕøÕ­Õ„Õ¬ վՄրև՝ Õ”Õ»"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ÕÕ„Õ²Õ”ÖƒÕøÕ­Õ„Õ¬ նՄրքև՝ Õ±Õ”Õ­"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ÕÕ„Õ²Õ”ÖƒÕøÕ­Õ„Õ¬ նՄրքև՝ Õ”Õ»"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>. Õ®Õ”Õ¾Õ”Õ¬Õ„Õ¬"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>. Õ®Õ”Õ¬Õ„Õ¬"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ÕÆÕ”Ö€Õ£Õ”Õ¾ÕøÖ€ÕøÖ‚Õ“Õ¶Õ„Ö€"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Õ“Õ”ÕÆÕ„Õ¬ Õ”Õ“ÕŗÕ«ÕÆÕØ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Զրույցը չցուցՔդրՄլ Õ”Õ“ÕŗÕ«ÕÆÕ« ÕæÕ„Õ½Ö„ÕøÕ¾"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ŌµÕ²Õ”Õ¾"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ō±Õ“ÕŗÕ«ÕÆÕ¶Õ„Ö€ Õ¹ÕÆÕ”Õ¶"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ō±ÕµÕ½ÕæÕ„Õ² կցուցՔդրվՄն Õ¾Õ„Ö€Õ»Õ„Ö€Õ½ Ö…Õ£ÕæÕ”Õ£ÕøÖ€Õ®Õ¾Õ”Õ® և փՔկվՔծ Õ”Õ“ÕŗÕ«ÕÆÕ¶Õ„Ö€ÕØ, ÕøÖ€ÕøÕ¶Ö„ ÕÆÕÆÕ”Ö€ÕøÕ²Õ”Õ¶Õ”Ö„ Õ°Õ„Õ·ÕæÕøÖ‚Õ©ÕµÕ”Õ“Õ¢ վՄրՔբՔցՄլ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Զրույցի Õ”Õ“ÕŗÕ«ÕÆÕ¶Õ„Ö€"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Õ†ÕøÖ€ զրույցնՄրը Õ°Õ”ÕµÕæÕ¶Õ¾ÕøÖ‚Õ“ Õ„Õ¶ ÕøÖ€ÕŗÕ„Õ½ ÕŗÕ”ÕæÕÆÕ„Ö€Õ”ÕÆÕ¶Õ„Ö€ Õ§ÕÆÖ€Õ”Õ¶Õ« Õ¶Õ„Ö€Ö„Ö‡Õ« Õ”Õ¶ÕÆÕµÕøÖ‚Õ¶ÕøÖ‚Õ“Ö‰ Õ€ÕŗÕ„Ö„Õ Õ¤Ö€Õ”Õ¶Ö„ Õ®Õ”Õ¾Õ”Õ¬Õ„Õ¬ÕøÖ‚, ÕÆÕ”Õ“ քՔշՄք՝ Õ“Õ„Ö€ÕŖÕ„Õ¬ÕøÖ‚ Õ°Õ”Õ“Õ”Ö€Ö‰"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Ō±Õ“ÕŗÕ«ÕÆÕ¶Õ„Ö€Õ« ÕÆÕ”Ö€Õ£Õ”Õ¾ÕøÖ€ÕøÖ‚Õ“Õ¶Õ„Ö€"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Õ€ÕŗÕ„Ö„ Õ”ÕµÕ½ÕæÕ„Õ²Õ ÕØÕ¶ÕæÖ€Õ„Õ¬ÕøÖ‚, Õ©Õ„ ÕøÖ€ Õ°Õ”Õ¾Õ„Õ¬Õ¾Õ”Õ®Õ¶Õ„Ö€Õ« և զրույցնՄրի Õ°Õ”Õ“Õ”Ö€ Õ”Õ“ÕŗÕ«ÕÆÕ¶Õ„Ö€ ցուցՔդրՄլ"</string>
<string name="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="4564728020654658478">"Õ€ÕŗÕ„Ö„Õ Õ°Õ”Õ¾Õ„Õ¬Õ¾Õ”Õ®ÕØ Õ¾Õ„Ö€Õ”Õ£ÕøÖ€Õ®Õ”Ö€ÕÆÕ„Õ¬ÕøÖ‚ և Õ”Õ¾Õ„Õ¬Õ« Õ°Õ”Ö€Õ“Õ”Ö€ ÕæÕ„Õ½Ö„ ÕØÕ¶ÕæÖ€Õ„Õ¬ÕøÖ‚ Õ°Õ”Õ“Õ”Ö€"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Õ“ÕøÕ­Õ„Õ¬ Õ°Õ”Õ¾Õ„Õ¬Õ¾Õ”Õ®Õ« ÕÆÕøÕ²Õ“Õ„Ö€Õ« հՔրՔբՄրՔկցությունը ŌæÕ”Ö€Õ£Õ”Õ¾ÕøÖ€ÕøÖ‚Õ“Õ¶Õ„Ö€ÕøÖ‚Õ“"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Õ“ÕøÕ­Õ„Õ¬ Õ¹Õ”ÖƒÕ„Ö€Õ« հՔրՔբՄրՔկցությունը"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ÕÕ„Õ½Õ”Õ­ÖÕ«ÕÆÕ« Õ°Õ„Õæ ÕÆÕ”ÕŗÕ¾Õ”Õ® Õ­Õ¶Õ¤Õ«Ö€Õ¶Õ„ÕžÖ€ ÕÆÕ”Õ¶Ö‰\nÕ€ÕŗÕ„Ö„Õ Õ¾Õ„Ö€Õ”ÕÆÕ”Ö€Õ£Õ”Õ¾ÕøÖ€Õ„Õ¬ÕøÖ‚ Õ°Õ”Õ“Õ”Ö€Ö‰"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Õ‰Õ°Õ”Õ»ÕøÕ²Õ¾Õ„ÕžÖ Õ·ÕæÕÆÕ„Õ¬Ö‰\nÕ€ÕŗÕ„Ö„Õ ÖƒÕøÖƒÕøÕ­ÕøÖ‚Õ©ÕµÕøÖ‚Õ¶Õ¶Õ„Ö€ÕØ Õ¹Õ„Õ²Õ”Ö€ÕÆÕ„Õ¬ÕøÖ‚ Õ°Õ”Õ“Õ”Ö€Ö‰"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ÕÕ„Õ½Õ”Õ­ÖÕ«ÕÆÕ« Õ°Õ„Õæ ÕÆÕ”ÕŗÕ¾Õ”Õ® Õ­Õ¶Õ¤Õ«Ö€Õ¶Õ„Ö€ Õ¹ÕÆÕ”ÕžÕ¶Ö‰ Õ“Õ”ÕÆÕ„Õ¬ÕøÖ‚ Õ°Õ”Õ“Õ”Ö€ Õ°ÕŗÕ„Ö„Ö‰"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 5747deb405ab..3dbd75087382 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pindahkan ke kanan atas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pindahkan ke kiri bawah"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pindahkan ke kanan bawah"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"luaskan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ciutkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setelan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Tutup balon"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan gunakan percakapan balon"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Oke"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tidak ada balon baru-baru ini"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Balon yang baru dipakai dan balon yang telah ditutup akan muncul di sini"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat dalam tampilan balon"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Percakapan baru muncul sebagai ikon di bagian pojok bawah layar. Ketuk untuk meluaskan percakapan atau tarik untuk menutup percakapan."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrol balon kapan saja"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketuk di sini untuk mengelola balon aplikasi dan percakapan"</string>
<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="4564728020654658478">"Ketuk untuk memulai ulang aplikasi ini agar mendapatkan tampilan yang lebih baik"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ubah rasio aspek aplikasi ini di Setelan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ubah rasio aspek"</string>
<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>
@@ -88,7 +96,7 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
- <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk melihat tampilan yang lebih baik?"</string>
<string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 145d26d35b75..2927d6a0d8ec 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"FƦra efst til hƦgri"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"FƦra neưst til vinstri"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"FƦrưu neưst til hƦgri"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"stƦkka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"minnka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Stillingar <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Loka blƶưru"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ekki setja samtal ƭ blƶưru"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ɖg skil"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Engar nýlegar blöðrur"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nýlegar blöðrur og blöðrur sem þú hefur lokað birtast hér"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Spjall meư blƶưrum"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Ný samtƶl birtast sem tĆ”kn neưst Ć” horni skjĆ”sins. Ɲttu til aư stƦkka þau eưa dragưu til aư hunsa þau."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Hægt er að stjórna blöðrum hvenær sem er"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ɲttu hĆ©r til aư stjórna þvĆ­ hvaưa forrit og samtƶl mega nota blƶưrur."</string>
<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="4564728020654658478">"Ɲttu til aư endurrƦsa forritiư og fĆ” betri sýn"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Breyta myndhlutfalli þessa forrits í stillingunum"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Breyta myndhlutfalli"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 025646cbb9cd..938a81466381 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sposta in alto a destra"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sposta in basso a sinistra"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sposta in basso a destra"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"espandi <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"comprimi <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Impostazioni <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora bolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Non mettere la conversazione nella bolla"</string>
@@ -76,17 +78,23 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta utilizzando le bolle"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Le nuove conversazioni vengono visualizzate sotto forma di icone in un angolo inferiore dello schermo. Tocca per espanderle o trascina per chiuderle."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string>
<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="4564728020654658478">"Tocca per riavviare l\'app e migliorare la visualizzazione"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Cambia le proporzioni dell\'app nelle Impostazioni"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Cambia proporzioni"</string>
<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="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
- <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string>
<string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinchƩ venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index bb3845b26a88..aaa0a3866031 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"העברה לפינה ×”×™×ž× ×™×Ŗ העליונה"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"העברה לפינה ×”×©×ž××œ×™×Ŗ התחתונה"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"העברה לפינה ×”×™×ž× ×™×Ŗ התחתונה"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"הרחבה של <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"כיווׄ של <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"הגדרות <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"הגירת בועה"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"אין להציג בועות לשיחה"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"הבנתי"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"אין בועות מהזמן ×”××—×Ø×•×Ÿ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"בועות אחרונות ובועות שנהגרו יופיעו כאן"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"צ\'אט בבועות"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"שיחות חדשות ×ž×•×¤×™×¢×•×Ŗ כהמלים בפינה התחתונה של המהך. אפשר להקיש כדי ×œ×”×Ø×—×™×‘ ××•×Ŗ×Ÿ או ×œ×’×Ø×•×Ø כדי ×œ×”×’×•×Ø ××•×Ŗ×Ÿ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"שליטה בבועות בכל זמן"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"אפשר להקיש כאן כדי לקבוע אילו ××¤×œ×™×§×¦×™×•×Ŗ ושיחות יוכלו להופיע בבועות"</string>
<string name="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="4564728020654658478">"כדי ×œ×Ø××•×Ŗ טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"אפשר ×œ×©× ×•×Ŗ את ×™×—×” הגובה-רוחב של האפליקציה הזו ב\'הגדרות\'"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"שינוי ×™×—×” גובה-רוחב"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע ×”×Ŗ××ž×” מחדש"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי ×œ×—×–×•×Ø ×œ×’×Ø×”×” ×”×§×•×“×ž×Ŗ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי ×œ×”×’×•×Ø."</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 6c1bafee9d82..1979fd52e5bb 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -54,7 +54,7 @@
<string name="accessibility_split_top" msgid="2789329702027147146">"äøŠć«åˆ†å‰²"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"äø‹ć«åˆ†å‰²"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ē‰‡ę‰‹ćƒ¢ćƒ¼ćƒ‰ć®ä½æē”Ø"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ēµ‚äŗ†ć™ć‚‹ć«ćÆć€ē”»é¢ć‚’äø‹ć‹ć‚‰äøŠć«ć‚¹ćƒÆć‚¤ćƒ—ć™ć‚‹ć‹ć€ć‚¢ćƒ—ćƒŖć®ä»»ę„ć®å “ę‰€ć‚’ć‚æćƒƒćƒ—ć—ć¾ć™"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ēµ‚äŗ†ć™ć‚‹ć«ćÆć€ē”»é¢ć‚’äø‹ć‹ć‚‰äøŠć«ć‚¹ćƒÆć‚¤ćƒ—ć™ć‚‹ć‹ć€ć‚¢ćƒ—ćƒŖć®äøŠå“ć®ä»»ę„ć®å “ę‰€ć‚’ć‚æćƒƒćƒ—ć—ć¾ć™"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ē‰‡ę‰‹ćƒ¢ćƒ¼ćƒ‰ć‚’é–‹å§‹ć—ć¾ć™"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"ē‰‡ę‰‹ćƒ¢ćƒ¼ćƒ‰ć‚’ēµ‚äŗ†ć—ć¾ć™"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> ć®ćƒćƒ–ćƒ«ć®čØ­å®š"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"å³äøŠć«ē§»å‹•"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"左下に移動"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"å³äø‹ć«ē§»å‹•"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ć‚’é–‹ćć¾ć™"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ć‚’é–‰ć˜ć¾ć™"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> の設定"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ćƒćƒ–ćƒ«ć‚’é–‰ć˜ć‚‹"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ä¼šč©±ć‚’ćƒćƒ–ćƒ«ć§č”Øē¤ŗć—ćŖć„"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ęœ€čæ‘é–‰ć˜ćŸćƒćƒ–ćƒ«ćÆć‚ć‚Šć¾ć›ć‚“"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ęœ€čæ‘č”Øē¤ŗć•ć‚ŒćŸćƒćƒ–ćƒ«ć‚„é–‰ć˜ćŸćƒćƒ–ćƒ«ćŒć€ć“ć“ć«č”Øē¤ŗć•ć‚Œć¾ć™"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ćƒćƒ£ćƒƒćƒˆć§ćƒćƒ–ćƒ«ć‚’ä½æć†"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ę–°ć—ć„ä¼šč©±ćŒć‚¢ć‚¤ć‚³ćƒ³ćØć—ć¦ē”»é¢äø‹éƒØć«č”Øē¤ŗć•ć‚Œć¾ć™ć€‚ć‚æćƒƒćƒ—ć™ć‚‹ćØé–‹ćć€ćƒ‰ćƒ©ćƒƒć‚°ć—ć¦é–‰ć˜ć‚‹ć“ćØćŒć§ćć¾ć™ć€‚"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ćƒćƒ–ćƒ«ćÆć„ć¤ć§ć‚‚ē®”ē†åÆčƒ½"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ćƒćƒ–ćƒ«ć§č”Øē¤ŗć™ć‚‹ć‚¢ćƒ—ćƒŖć‚„ä¼šč©±ć‚’ē®”ē†ć™ć‚‹ć«ćÆć€ć“ć“ć‚’ć‚æćƒƒćƒ—ć—ć¾ć™"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ćƒćƒ–ćƒ«"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"箔理"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ćµćć ć—ćŒéžč”Øē¤ŗć«ćŖć£ć¦ć„ć¾ć™ć€‚"</string>
- <string name="restart_button_description" msgid="6712141648865547958">"ć‚æćƒƒćƒ—ć—ć¦ć“ć®ć‚¢ćƒ—ćƒŖć‚’å†čµ·å‹•ć™ć‚‹ćØć€č”Øē¤ŗćŒé©åˆ‡ć«ćŖć‚Šć¾ć™ć€‚"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"ć‚æćƒƒćƒ—ć—ć¦ć“ć®ć‚¢ćƒ—ćƒŖć‚’å†čµ·å‹•ć™ć‚‹ćØć€č”Øē¤ŗćŒé©åˆ‡ć«ćŖć‚Šć¾ć™"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ć“ć®ć‚¢ćƒ—ćƒŖć®ć‚¢ć‚¹ćƒšć‚ÆćƒˆęÆ”ć‚’ [設定] で変曓します"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ć‚¢ć‚¹ćƒšć‚ÆćƒˆęÆ”ć‚’å¤‰ę›“"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ć‚«ćƒ”ćƒ©ć«é–¢ć™ć‚‹å•é”Œć®å “åˆćÆć€\nć‚æćƒƒćƒ—ć™ć‚‹ćØäæ®ę­£ć§ćć¾ć™"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"äæ®ę­£ć•ć‚ŒćŖć‹ć£ćŸå “åˆćÆć€\nć‚æćƒƒćƒ—ć™ć‚‹ćØå…ƒć«ęˆ»ć‚Šć¾ć™"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ć‚«ćƒ”ćƒ©ć«é–¢ć™ć‚‹å•é”Œć§ćŖć„å “åˆćÆć€ć‚æćƒƒćƒ—ć™ć‚‹ćØé–‰ć˜ć¾ć™ć€‚"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index e58e67ab36cb..ce1ce6402637 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"įƒ’įƒįƒ“įƒįƒįƒœįƒįƒŖįƒ•įƒšįƒ”įƒ— įƒ–įƒ”įƒ•įƒ˜įƒ— įƒ“įƒ įƒ›įƒįƒ įƒÆįƒ•įƒœįƒ˜įƒ•"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"įƒ„įƒ•įƒ”įƒ•įƒ˜įƒ— įƒ“įƒ įƒ›įƒįƒ įƒŖįƒ®įƒœįƒ˜įƒ• įƒ’įƒįƒ“įƒįƒ¢įƒįƒœįƒ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"įƒ’įƒįƒ“įƒįƒįƒœįƒįƒŖįƒ•. įƒ„įƒ•įƒ”įƒ›įƒįƒ— įƒ“įƒ įƒ›įƒįƒ įƒÆįƒ•įƒœįƒ˜įƒ•"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-იე įƒ’įƒįƒ¤įƒįƒ įƒ—įƒįƒ”įƒ‘įƒ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-იე įƒ©įƒįƒ™įƒ”įƒŖįƒ•įƒ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-იე įƒžįƒįƒ įƒįƒ›įƒ”įƒ¢įƒ įƒ”įƒ‘įƒ˜"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"įƒ‘įƒ£įƒØįƒ¢įƒ˜įƒ” įƒ“įƒįƒ®įƒ£įƒ įƒ•įƒ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"įƒįƒ˜įƒ™įƒ įƒ«įƒįƒšįƒįƒ” įƒ”įƒįƒ£įƒ‘įƒ įƒ˜įƒ” įƒ‘įƒ£įƒØįƒ¢įƒ”įƒ‘įƒ˜"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"įƒ’įƒįƒ”įƒįƒ’įƒ”įƒ‘įƒ˜įƒ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"įƒ‘įƒįƒšįƒ įƒ“įƒ įƒįƒ” įƒ’įƒįƒ›įƒįƒ§įƒ”įƒœįƒ”įƒ‘įƒ£įƒšįƒ˜ įƒ‘įƒ£įƒØįƒ¢įƒ”įƒ‘įƒ˜ არ არიე"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"აჄ įƒ’įƒįƒ›įƒįƒ©įƒœįƒ“įƒ”įƒ‘įƒ įƒ‘įƒįƒšįƒįƒ“įƒ įƒįƒ˜įƒœįƒ“įƒ”įƒšįƒ˜ įƒ‘įƒ£įƒØįƒ¢įƒ”įƒ‘įƒ˜ įƒ“įƒ įƒ£įƒįƒ įƒ§įƒįƒ¤įƒ˜įƒšįƒ˜ įƒ‘įƒ£įƒØįƒ¢įƒ”įƒ‘įƒ˜"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"įƒ©įƒ”įƒ—įƒ˜ įƒ‘įƒ£įƒØįƒ¢įƒ”įƒ‘įƒ˜įƒ” įƒ’įƒįƒ›įƒįƒ§įƒ”įƒœįƒ”įƒ‘įƒ˜įƒ—"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ახალი įƒ”įƒįƒ£įƒ‘įƒ įƒ”įƒ‘įƒ˜ ხატულაე įƒ”įƒįƒ®įƒ˜įƒ— įƒ’įƒįƒ›įƒįƒ©įƒœįƒ“įƒ”įƒ‘įƒ įƒ—įƒ„įƒ•įƒ”įƒœįƒ˜ įƒ”įƒ™įƒ įƒįƒœįƒ˜įƒ” įƒ„įƒ•įƒ”įƒ“įƒ įƒ™įƒ£įƒ—įƒ®įƒ”įƒØįƒ˜. įƒØįƒ”įƒ”įƒ®įƒ”įƒ— įƒ’įƒįƒ”įƒįƒ¤įƒįƒ įƒ—įƒįƒ”įƒ‘įƒšįƒįƒ“ įƒ“įƒ įƒ©įƒįƒįƒ•įƒšįƒ”įƒ— įƒ›įƒįƒ— įƒ“įƒįƒ”įƒįƒ®įƒ£įƒ įƒįƒ“."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"įƒįƒ›įƒįƒ®įƒ¢įƒįƒ›įƒ˜įƒ” įƒ’įƒįƒ™įƒįƒœįƒ¢įƒ įƒįƒšįƒ”įƒ‘įƒ įƒœįƒ”įƒ‘įƒ˜įƒ”įƒ›įƒ˜įƒ”įƒ  įƒ“įƒ įƒįƒ”"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"აჄ įƒØįƒ”įƒ”įƒ®įƒ”įƒ— įƒ˜įƒ›įƒ˜įƒ” įƒ”įƒįƒ›įƒįƒ įƒ—įƒįƒ•įƒįƒ“, įƒ—įƒ£ įƒ įƒįƒ›įƒ”įƒšįƒ˜ įƒįƒžįƒ”įƒ‘įƒ˜ įƒ“įƒ įƒ”įƒįƒ£įƒ‘įƒ įƒ”įƒ‘įƒ˜ įƒįƒ›įƒįƒ®įƒ¢įƒ”įƒ”"</string>
<string name="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="4564728020654658478">"įƒØįƒ”įƒ®įƒ”įƒ‘įƒ˜įƒ— įƒ’įƒįƒ“įƒįƒ¢įƒ•įƒ˜įƒ įƒ—įƒ”įƒ— įƒ”įƒ” įƒįƒžįƒ˜ įƒ£įƒ™įƒ”įƒ—įƒ”įƒ”įƒ˜ įƒ®įƒ”įƒ“įƒ˜įƒ” įƒ›įƒ˜įƒ”įƒįƒ¦įƒ”įƒ‘įƒįƒ“"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"įƒØįƒ”įƒŖįƒ•įƒįƒšįƒ”įƒ— įƒįƒ› įƒįƒžįƒ˜įƒ” įƒ—įƒįƒœįƒįƒ¤įƒįƒ įƒ“įƒįƒ‘įƒ įƒžįƒįƒ įƒįƒ›įƒ”įƒ¢įƒ įƒ”įƒ‘įƒ˜įƒ“įƒįƒœ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"įƒ—įƒįƒœįƒįƒ¤įƒįƒ įƒ“įƒįƒ‘įƒ˜įƒ” įƒØįƒ”įƒŖįƒ•įƒšįƒ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"įƒ™įƒįƒ›įƒ”įƒ įƒįƒ“ įƒžįƒ įƒįƒ‘įƒšįƒ”įƒ›įƒ”įƒ‘įƒ˜ įƒįƒ„įƒ•įƒ”?\nįƒØįƒ”įƒ”įƒ®įƒ”įƒ— įƒ’įƒįƒ›įƒįƒ”įƒįƒ”įƒ¬įƒįƒ įƒ”įƒ‘įƒšįƒįƒ“"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ įƒ’įƒįƒ›įƒįƒ”įƒ¬įƒįƒ įƒ“įƒ?\nįƒØįƒ”įƒ”įƒ®įƒ”įƒ— წინა įƒ•įƒ”įƒ įƒ”įƒ˜įƒ˜įƒ” įƒ“įƒįƒ”įƒįƒ‘įƒ įƒ£įƒœįƒ”įƒ‘įƒšįƒįƒ“"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"įƒ™įƒįƒ›įƒ”įƒ įƒįƒ” įƒžįƒ įƒįƒ‘įƒšįƒ”įƒ›įƒ”įƒ‘įƒ˜ არ įƒįƒ„įƒ•įƒ”? įƒØįƒ”įƒ”įƒ®įƒ”įƒ— įƒ£įƒįƒ įƒ§įƒįƒ¤įƒ˜įƒ”įƒ—įƒ•įƒ˜įƒ”."</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 7c9120e22b30..b4b05072f4a4 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жоғары оң жаққа Š¶Ń‹Š»Š¶Ń‹Ń‚Ńƒ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төменгі сол жаққа Š¶Ń‹Š»Š¶Ń‹Ń‚Ńƒ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төменгі оң жаққа Š¶Ń‹Š»Š¶Ń‹Ń‚Ńƒ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: Š¶Š°ŃŽ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>: Š¶ŠøŃŽ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлері"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŅšŠ°Š»Ņ›Ń‹Š¼Š°Š»Ń‹ хабарГы жабу"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ó˜Ņ£Š³Ń–Š¼ŠµŠ½Ń–Ņ£ қалқыма хабары көрсетілмесін"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түсінікті"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ЖақынГағы қалқыма хабарлар жоқ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Доңғы және жабылған қалқыма хабарлар осы жерГе көрсетілеГі."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ŅšŠ°Š»Ņ›Ń‹Š¼Š° хабарлар арқылы чатта сөйлесу"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Жаңа әңгімелер ŃŠŗŃ€Š°Š½Š½Ń‹Ņ£ төменгі бөлігінГе Š±ŠµŠ»Š³Ń–ŃˆŠµ түрінГе көрсетілеГі. ŠžŠ»Š°Ń€Š“Ń‹ Š¶Š°ŃŽ ŅÆŃˆŃ–Š½ түртіңіз, ал жабу ŅÆŃˆŃ–Š½ сүйреңіз."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŅšŠ°Š»Ņ›Ń‹Š¼Š° хабарларГы кез келген ŃƒŠ°Ņ›Ń‹Ń‚Ń‚Š° басқарыңыз"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ŅšŠ°Š»Ņ›Ń‹Š¼Š° хабарГа көрсетілетін қолГанбалар мен әңгімелерГі Ń€ŠµŃ‚Ń‚ŠµŃƒ ŅÆŃˆŃ–Š½ осы жерГі түртіңіз."</string>
<string name="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="4564728020654658478">"ŠšÓ©Ń€Ń–Š½Ń–ŃŃ‚Ń– Š¶Š°Ņ›ŃŠ°Ń€Ń‚Ńƒ ŅÆŃˆŃ–Š½ осы қолГанбаны түртіп, қайта Š°ŃˆŃ‹Ņ£Ń‹Š·."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ŠžŃŃ‹ қолГанбаның арақатынасын параметрлерГен Ó©Š·Š³ŠµŃ€Ń‚ŃƒŠ³Šµ болаГы."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Арақатынасты Ó©Š·Š³ŠµŃ€Ń‚Ńƒ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠšŠ°Š¼ŠµŃ€Š°Š“Š° қателер ŃˆŃ‹Ņ›Ń‚Ń‹ ма?\nŠ–Ó©Š½Š“ŠµŃƒ ŅÆŃˆŃ–Š½ түртіңіз."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ЖөнГелмеГі ме?\nŅšŠ°Š¹Ń‚Š°Ń€Ńƒ ŅÆŃˆŃ–Š½ түртіңіз."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠšŠ°Š¼ŠµŃ€Š°Š“Š° қателер ŃˆŃ‹Ņ›ŠæŠ°Š“Ń‹ ма? Š–Š°Š±Ńƒ ŅÆŃˆŃ–Š½ түртіңіз."</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 45302677a043..9b0a0dad5782 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"įž•įŸ’įž›įž¶įžŸįŸ‹įž‘įžøįž‘įŸ…įž•įŸ’įž“įŸ‚įž€įžįž¶įž„įž›įž¾įžįž¶įž„įžŸįŸ’įžŠįž¶įŸ†"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"įž•įŸ’įž›įž¶įžŸįŸ‹įž‘įžøįž‘įŸ…įž•įŸ’įž“įŸ‚įž€įžįž¶įž„įž€įŸ’įžšįŸ„įž˜įžįž¶įž„įž†įŸ’įžœįŸįž„ā€‹"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"įž•įŸ’įž›įž¶įžŸįŸ‹įž‘įžøįž‘įŸ…įž•įŸ’įž“įŸ‚įž€įžįž¶įž„įž€įŸ’įžšįŸ„įž˜ā€‹įžįž¶įž„įžŸįŸ’įžŠįž¶įŸ†"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"įž–įž„įŸ’įžšįžøįž€ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"įž”įž„įŸ’įžšįž½įž˜ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"įž€įž¶įžšįž€įŸ†įžŽįžįŸ‹ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"įž…įŸ’įžšįž¶įž“įž…įŸ„įž›ā€‹įž–įž–įž»įŸ‡"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"įž€įž»įŸ†įž”įž„įŸ’įž įž¶įž‰ā€‹įž€įž¶įžšįžŸįž“įŸ’įž‘įž“įž¶ā€‹įž‡įž¶įž–įž–įž»įŸ‡"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"įž™įž›įŸ‹įž įž¾įž™"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"įž˜įž·įž“įž˜įž¶įž“ā€‹įž–įž–įž»įŸ‡ā€‹įžįŸ’įž˜įžøįŸ—ā€‹įž‘įŸ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"įž–įž–įž»įŸ‡įžįŸ’įž˜įžøįŸ—ā€‹ įž“įž·įž„ā€‹įž–įž–įž»įŸ‡įžŠįŸ‚įž›įž”įž¶įž“įž”įž·įž‘ā€‹ā€‹įž“įž¹įž„ā€‹įž”įž„įŸ’įž įž¶įž‰ā€‹įž“įŸ…įž‘įžøįž“įŸįŸ‡"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"įž‡įž‡įŸ‚įž€ā€‹įžŠįŸ„įž™įž”įŸ’įžšįž¾ā€‹įž•įŸ’įž‘įž¶įŸ†įž„įž¢įžŽįŸ’įžŠįŸ‚įž"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"įž€įž¶įžšįžŸįž“įŸ’įž‘įž“įž¶įžįŸ’įž˜įžøįŸ—įž”įž„įŸ’įž įž¶įž‰įž‡įž¶įžšįž¼įž”įžįŸ†įžŽįž¶įž„įž“įŸ…įž‡įŸ’įžšįž»įž„įžįž¶įž„įž€įŸ’įžšįŸ„įž˜įž“įŸƒįž¢įŸįž€įŸ’įžšįž„įŸ‹įžšįž”įžŸįŸ‹įž¢įŸ’įž“įž€įŸ” įžŸįž¼įž˜įž…įž»įž… įžŠįž¾įž˜įŸ’įž”įžøįž–įž„įŸ’įžšįžøįž€įž€įž¶įžšįžŸįž“įŸ’įž‘įž“įž¶įž‘įž¶įŸ†įž„įž“įŸ„įŸ‡ įž¬įž¢įž¼įžŸ įžŠįž¾įž˜įŸ’įž”įžøįž…įŸ’įžšįž¶įž“įž…įŸ„įž›įŸ”"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"įž‚įŸ’įžšįž”įŸ‹įž‚įŸ’įžšįž„ā€‹įž•įŸ’įž‘įž¶įŸ†įž„įž¢įžŽįŸ’įžŠįŸ‚įžįž“įŸ…įž–įŸįž›įžŽįž¶įž€įŸįž”įž¶įž“"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"įž…įž»įž…įžįŸ’įžšįž„įŸ‹įž“įŸįŸ‡ įžŠįž¾įž˜įŸ’įž”įžøįž‚įŸ’įžšįž”įŸ‹įž‚įŸ’įžšįž„įž€įž˜įŸ’įž˜įžœįž·įž’įžø įž“įž·įž„įž€įž¶įžšįžŸįž“įŸ’įž‘įž“įž¶įžŠįŸ‚įž›įž¢įž¶įž…įž”įž„įŸ’įž įž¶įž‰įž‡įž¶įž•įŸ’įž‘įž¶įŸ†įž„įž¢įžŽįŸ’įžŠįŸ‚įž"</string>
<string name="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="4564728020654658478">"įž…įž»įž…įžŠįž¾įž˜įŸ’įž”įžøā€‹įž…įž¶įž”įŸ‹įž•įŸ’įžŠįž¾įž˜ā€‹įž€įž˜įŸ’įž˜įžœįž·įž’įžøā€‹įž“įŸįŸ‡įž”įž¾įž„įžœįž·įž‰įžŸįž˜įŸ’įžšįž¶įž”įŸ‹įž‘įž·įžŠįŸ’įž‹įž—įž¶įž–įž€įž¶įž“įŸ‹įžįŸ‚įž”įŸ’įžšįžŸįž¾įžš"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"įž•įŸ’įž›įž¶įžŸįŸ‹įž”įŸ’įžŠįž¼įžšįžŸįž˜įž¶įž˜įž¶įžįŸ’įžšā€‹įžšįž”įžŸįŸ‹įž€įž˜įŸ’įž˜įžœįž·įž’įžøįž“įŸįŸ‡įž“įŸ…įž€įŸ’įž“įž»įž„įž€įž¶įžšįž€įŸ†įžŽįžįŸ‹"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"įž”įŸ’įžŠįž¼įžšā€‹ā€‹įžŸįž˜įž¶įž˜įž¶įžįŸ’įžš"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"įž˜įž¶įž“įž”įž‰įŸ’įž įž¶ā€‹įž–įž¶įž€įŸ‹įž–įŸįž“įŸ’įž’įž“įž¹įž„ā€‹įž€įž¶įž˜įŸįžšįŸ‰įž¶įž¬?\nįž…įž»įž…įžŠįž¾įž˜įŸ’įž”įžøā€‹įžŠįŸ„įŸ‡įžŸįŸ’įžšįž¶įž™"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"įž˜įž·įž“įž”įž¶įž“ā€‹įžŠįŸ„įŸ‡įžŸįŸ’įžšįž¶įž™ā€‹įž”įž‰įŸ’įž įž¶įž“įŸįŸ‡įž‘įŸįž¬?\nįž…įž»įž…įžŠįž¾įž˜įŸ’įž”įžøā€‹įžįŸ’įžšįž”įž”įŸ‹"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"įž˜įž·įž“įž˜įž¶įž“ā€‹įž”įž‰įŸ’įž įž¶įž–įž¶įž€įŸ‹įž–įŸįž“įŸ’įž’įž“įž¹įž„ā€‹įž€įž¶įž˜įŸįžšįŸ‰įž¶įž‘įŸįž¬? įž…įž»įž…įžŠįž¾įž˜įŸ’įž”įžøā€‹įž…įŸ’įžšįž¶įž“įž…įŸ„įž›įŸ”"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 2dfbad49bb06..b19978aa09d7 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ಬಲ ą²®ą³‡ą²²ą³ą²­ą²¾ą²—ą²•ą³ą²•ą³† ಸರಿಸಿ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą²øą³ą²•ą³ą²°ą³€ą²Øą³ā€Œą²Ø ą²Žą²” ą²•ą³†ą²³ą²­ą²¾ą²—ą²•ą³ą²•ą³† ಸರಿಸಿ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ಕೆಳಗಿನ ą²¬ą²²ą²­ą²¾ą²—ą²•ą³ą²•ą³† ಸರಿಸಿ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą²…ą²Øą³ą²Øą³ ą²µą²æą²øą³ą²¤ą³ƒą²¤ą²—ą³Šą²³ą²æą²øą²æ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą²…ą²Øą³ą²Øą³ ą²•ą³ą²—ą³ą²—ą²æą²øą²æ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą²øą³†ą²Ÿą³ą²Ÿą²æą²‚ą²—ą³ā€Œą²—ą²³ą³"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ą²¬ą²¬ą²²ą³ ą²µą²œą²¾ą²—ą³Šą²³ą²æą²øą²æ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą²øą²‚ą²­ą²¾ą²·ą²£ą³†ą²Æą²Øą³ą²Øą³ ą²¬ą²¬ą²²ą³ ಮಾಔಬೇಔಿ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą²…ą²°ą³ą²„ą²µą²¾ą²Æą²æą²¤ą³"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ಯಾವುದೇ ą²‡ą²¤ą³ą²¤ą³€ą²šą²æą²Ø ą²¬ą²¬ą²²ą³ą²øą³ ą²‡ą²²ą³ą²²"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą²‡ą²¤ą³ą²¤ą³€ą²šą²æą²Ø ą²¬ą²¬ą²²ą³ą²øą³ ą²®ą²¤ą³ą²¤ą³ ą²µą²œą²¾ą²—ą³Šą²³ą²æą²øą²æą²¦ ą²¬ą²¬ą²²ą³ą²øą³ ą²‡ą²²ą³ą²²ą²æ ą²—ą³‹ą²šą²°ą²æą²øą³ą²¤ą³ą²¤ą²µą³†"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą²¬ą²¬ą²²ą³ą²øą³ ಬಳಸಿ ą²šą²¾ą²Ÿą³ ಮಾಔಿ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ಹೊಸ ಸಂಭಾಷಣೆಗಳು ą²Øą²æą²®ą³ą²® ą²øą³ą²•ą³ą²°ą³€ą²Øą³ ಕೆಳಗಿನ ą²®ą³‚ą²²ą³†ą²Æą²²ą³ą²²ą²æ ą²ą²•ą²¾ą²Øą³ā€Œą²—ą²³ą²¾ą²—ą²æ ą²—ą³‹ą²šą²°ą²æą²øą³ą²¤ą³ą²¤ą²µą³†. ą²µą²æą²øą³ą²¤ą²°ą²æą²øą²²ą³ ą²…ą²µą³ą²—ą²³ą²Øą³ą²Øą³ ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ ಅ಄ವಾ ą²µą²œą²¾ą²—ą³Šą²³ą²æą²øą²²ą³ ą²”ą³ą²°ą³ą²Æą²¾ą²—ą³ ಮಾಔಿ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ಯಾವುದೇ ą²øą²®ą²Æą²¦ą²²ą³ą²²ą²æ ą²¬ą²¬ą²²ą³ą²øą³ ą²…ą²Øą³ą²Øą³ ą²Øą²æą²Æą²‚ą²¤ą³ą²°ą²æą²øą²æ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ಯಾವ ą²†ą³ą²Æą²Ŗą³ā€Œą²—ą²³ą³ ą²®ą²¤ą³ą²¤ą³ ą²øą²‚ą²­ą²¾ą²·ą²£ą³†ą²—ą²³ą²Øą³ą²Øą³ ą²¬ą²¬ą²²ą³ ಮಾಔಬಹುದು ą²Žą²‚ą²¬ą³ą²¦ą²Øą³ą²Øą³ ą²Øą²æą²°ą³ą²µą²¹ą²æą²øą²²ą³ ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ"</string>
<string name="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="4564728020654658478">"ą²‰ą²¤ą³ą²¤ą²® ą²µą³€ą²•ą³ą²·ą²£ą³†ą²—ą²¾ą²—ą²æ ಈ ą²†ą³ą²Æą²Ŗą³ ą²…ą²Øą³ą²Øą³ ą²®ą²°ą³ą²Ŗą³ą²°ą²¾ą²°ą²‚ą²­ą²æą²øą²²ą³ ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą²øą³†ą²Ÿą³ą²Ÿą²æą²‚ą²—ą³ā€Œą²—ą²³ą²²ą³ą²²ą²æ ಈ ą²†ą³ą²Æą²Ŗą³ā€Œą²Ø ą²¦ą³ƒą²¶ą³ą²Æą²¾ą²Øą³ą²Ŗą²¾ą²¤ą²µą²Øą³ą²Øą³ ಬದಲಾಯಿಸಿ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą²¦ą³ƒą²¶ą³ą²Æą²¾ą²Øą³ą²Ŗą²¾ą²¤ą²µą²Øą³ą²Øą³ ಬದಲಾಯಿಸಿ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą²•ą³ą²Æą²¾ą²®ą²°ą²¾ ą²øą²®ą²øą³ą²Æą³†ą²—ą²³ą²æą²µą³†ą²Æą³‡?\ną²®ą²°ą³ą²¹ą³Šą²‚ą²¦ą²æą²øą²²ą³ ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą²…ą²¦ą²Øą³ą²Øą³ ą²øą²°ą²æą²Ŗą²”ą²æą²øą²²ą²æą²²ą³ą²²ą²µą³‡?\nಹಿಂತಿರುಗಿಸಲು ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą²•ą³ą²Æą²¾ą²®ą²°ą²¾ ą²øą²®ą²øą³ą²Æą³†ą²—ą²³ą²æą²²ą³ą²²ą²µą³‡? ą²µą²œą²¾ą²—ą³Šą²³ą²æą²øą²²ą³ ą²Ÿą³ą²Æą²¾ą²Ŗą³ ಮಾಔಿ."</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 39d717dd461a..af08da9d6e65 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"오넸쪽 ģƒė‹Øģœ¼ė”œ ģ“ė™"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"왼쪽 ķ•˜ė‹Øģœ¼ė”œ ģ“ė™"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"오넸쪽 ķ•˜ė‹Øģœ¼ė”œ ģ“ė™"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ķŽ¼ģ¹˜źø°"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ģ ‘źø°"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> 설정"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ėŒ€ķ™”ģ°½ ė‹«źø°"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ėŒ€ķ™”ė„¼ ėŒ€ķ™”ģ°½ģœ¼ė”œ ķ‘œģ‹œķ•˜ģ§€ ģ•Šźø°"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ķ™•ģø"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"최근 ėŒ€ķ™”ģ°½ ģ—†ģŒ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"최근 ėŒ€ķ™”ģ°½ź³¼ ė‚“ź°€ ė‹«ģ€ ėŒ€ķ™”ģ°½ģ“ 여기에 ķ‘œģ‹œė©ė‹ˆė‹¤."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ėŒ€ķ™”ģ°½ģœ¼ė”œ ģ±„ķŒ…ķ•˜źø°"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"새 ėŒ€ķ™”ėŠ” 화멓 ķ•˜ė‹Øģ— ģ•„ģ“ģ½˜ģœ¼ė”œ ķ‘œģ‹œė©ė‹ˆė‹¤. ģ•„ģ“ģ½˜ģ„ ķƒ­ķ•˜ģ—¬ ķŽ¼ģ¹˜ź±°ė‚˜ ė“œėž˜ź·øķ•˜ģ—¬ ė‹«ģŠµė‹ˆė‹¤."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ģ–øģ œė“ ģ§€ ėŒ€ķ™”ģ°½ģ„ ģ œģ–“ķ•˜ģ„øģš”"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ėŒ€ķ™”ģ°½ģ„ ė§Œė“¤ 수 ģžˆėŠ” 앱과 ėŒ€ķ™”ė„¼ ź“€ė¦¬ķ•˜ė ¤ė©“ 여기넼 ķƒ­ķ•˜ģ„øģš”."</string>
<string name="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="4564728020654658478">"ķƒ­ķ•˜ė©“ ģ•±ģ„ ė‹¤ģ‹œ ģ‹œģž‘ķ•˜ģ—¬ 볓기넼 ź°œģ„ ķ•©ė‹ˆė‹¤."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ģ„¤ģ •ģ—ģ„œ ģ•±ģ˜ ź°€ė”œģ„øė”œ ė¹„ģœØģ„ ė³€ź²½ķ•©ė‹ˆė‹¤."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ź°€ė”œģ„øė”œ ė¹„ģœØ 변경"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ģ¹“ė©”ė¼ ė¬øģ œź°€ ģžˆė‚˜ģš”?\nķ•“ź²°ķ•˜ė ¤ė©“ ķƒ­ķ•˜ģ„øģš”."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ķ•“ź²°ė˜ģ§€ ģ•Šģ•˜ė‚˜ģš”?\nė˜ėŒė¦¬ė ¤ė©“ ķƒ­ķ•˜ģ„øģš”."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ģ¹“ė©”ė¼ģ— ė¬øģ œź°€ ģ—†ė‚˜ģš”? ė‹«ģœ¼ė ¤ė©“ ķƒ­ķ•˜ģ„øģš”."</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index f210ea29da00..be24cefbaf91 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"ŠœŠµŠ½ŃŽ"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Дүрөт ичинГеги сүрөт Š¼ŠµŠ½ŃŽŃŃƒ"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичинГеги сүрөт"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колГонмосу бул Ń„ŃƒŠ½ŠŗŃ†ŠøŃŠ½Ń‹ пайГаланбасын Гесеңиз, жөнГөөлөрГү ачып Ń‚ŃƒŃ€ŃƒŠæ, аны өчүрүп ŠŗŠ¾ŃŽŅ£ŃƒŠ·."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колГонмосу бул Ń„ŃƒŠ½ŠŗŃ†ŠøŃŠ½Ń‹ пайГаланбасын Гесеңиз, параметрлерГи ачып Ń‚ŃƒŃ€ŃƒŠæ, аны өчүрүп ŠŗŠ¾ŃŽŅ£ŃƒŠ·."</string>
<string name="pip_play" msgid="3496151081459417097">"ŠžŠ¹Š½Š¾Ń‚ŃƒŃƒ"</string>
<string name="pip_pause" msgid="690688849510295232">"Š¢Ń‹Š½Š“Ń‹Ń€ŃƒŃƒ"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Кийинкисине өткөрүп жиберүү"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Š–Š¾Š³Š¾Ń€ŠŗŃƒ оң жакка Š¶Ń‹Š»Š“Ń‹Ń€ŃƒŃƒ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төмөнкү сол жакка Š¶Ń‹Š»Š“Ń‹Ń€ŃƒŃƒ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка Š¶Ń‹Š»Š“Ń‹Ń€ŃƒŃƒ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> жайып көрсөтүү"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> Š¶Ń‹Š¹Ń‹ŃˆŃ‚Ń‹Ń€ŃƒŃƒ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŠšŠ°Š»ŠŗŃ‹Šæ чыкма билГирмени жабуу"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Š–Š°Š·Ń‹ŃˆŃƒŃƒŠ“Š° калкып чыкма билГирмелер көрүнбөсүн"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ТүшүнГүм"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Азырынча эч нерсе жок"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Акыркы жана жабылган калкып чыкма билГирмелер ушул жерГе көрүнөт"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ŠšŠ°Š»ŠŗŃ‹Šæ чыкма билГирмелер Š°Ń€ŠŗŃ‹Š»ŃƒŃƒ Š¼Š°ŠµŠŗŃ‚ŠµŃˆŅÆŅÆ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Жаңы ŃŅÆŠ¹Š»Ó©ŃˆŅÆŅÆŠ»Ó©Ń€ ŃŠŗŃ€Š°Š½Ń‹Ņ£Ń‹Š·Š“Ń‹Š½ төмөнкү Š±ŃƒŃ€Ń‡ŃƒŠ½Š“Š° сүрөтчөлөр катары көрүнөт. АларГы жайып көрсөтүү үчүн таптап же четке кагуу үчүн сүйрөңүз."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŠšŠ°Š»ŠŗŃ‹Šæ чыкма билГирмелерГи каалаган ŃƒŠ±Š°ŠŗŃ‚Š° көзөмөлГөңүз"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ŠšŠ°Š»ŠŗŃ‹Šæ чыкма билГирме түрүнГө көрүнө Ń‚ŃƒŃ€Š³Š°Š½ колГонмолор менен маектерГи танГоо үчүн бул жерГи таптаңыз"</string>
<string name="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="4564728020654658478">"Š–Š°ŠŗŃˆŃ‹Ń€Š°Š°Šŗ көрүү үчүн бул колГонмону өчүрүп күйгүзүңүз. Ал үчүн таптап ŠŗŠ¾ŃŽŅ£ŃƒŠ·"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Š‘ŃƒŠ» колГонмонун тараптарынын ŠŗŠ°Ń‚Š½Š°ŃˆŃ‹Š½ параметрлерГен өзгөртүү"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ТараптарГын ŠŗŠ°Ń‚Š½Š°ŃˆŃ‹Š½ өзгөртүү"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠšŠ°Š¼ŠµŃ€Š°Š“Š° маселелер келип чыктыбы?\nŠžŅ£Š“Š¾Š¾ үчүн таптаңыз"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠžŅ£Š“Š¾Š»Š³Š¾Š½ жокпу?\nАртка ŠŗŠ°Š¹Ń‚Š°Ń€ŃƒŃƒ үчүн таптаңыз"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠšŠ°Š¼ŠµŃ€Š°Š“Š° маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index a25699fa4d10..41219eec986c 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ąŗą»‰ąŗ²ąŗąŗ‚ąŗ§ąŗ²ą»€ąŗ—ąŗ“ąŗ‡"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ąŗą»‰ąŗ²ąŗąŗŠą»‰ąŗ²ąŗąŗ„ąŗøą»ˆąŗ”"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ąŗą»‰ąŗ²ąŗąŗ‚ąŗ§ąŗ²ąŗ„ąŗøą»ˆąŗ”"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ąŗ‚ąŗ°ąŗ«ąŗąŗ²ąŗ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ąŗ«ąŗą»ą»‰ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ຄົງ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ąŗąŗ²ąŗ™ąŗ•ąŗ±ą»‰ąŗ‡ąŗ„ą»ˆąŗ² <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ąŗ›ąŗ“ąŗ”ąŗŸąŗ­ąŗ‡ą»„ąŗ§ą»‰"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ąŗ¢ą»ˆąŗ²ą»ƒąŗŠą»‰ąŗŸąŗ­ąŗ‡ą»ƒąŗ™ąŗąŗ²ąŗ™ąŗŖąŗ»ąŗ™ąŗ—ąŗ°ąŗ™ąŗ²"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą»€ąŗ‚ąŗ»ą»‰ąŗ²ą»ƒąŗˆą»ąŗ„ą»‰ąŗ§"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ąŗšą»ą»ˆąŗ”ąŗµąŗŸąŗ­ąŗ‡ąŗ«ąŗ¼ą»‰ąŗ²ąŗŖąŗøąŗ”"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ąŗŸąŗ­ąŗ‡ąŗ«ąŗ¼ą»‰ąŗ²ąŗŖąŗøąŗ” ແຄະ ąŗŸąŗ­ąŗ‡ąŗ—ąŗµą»ˆąŗ›ąŗ“ąŗ”ą»„ąŗ›ąŗˆąŗ°ąŗ›ąŗ²ąŗąŗ»ąŗ”ąŗ¢ąŗ¹ą»ˆąŗšą»ˆąŗ­ąŗ™ąŗ™ąŗµą»‰"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ąŗŖąŗ»ąŗ™ąŗ—ąŗ°ąŗ™ąŗ²ą»‚ąŗ”ąŗą»ƒąŗŠą»‰ąŗŸąŗ­ąŗ‡"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ąŗąŗ²ąŗ™ąŗŖąŗ»ąŗ™ąŗ—ąŗ°ąŗ™ąŗ²ą»ƒą»ą»ˆą»†ąŗˆąŗ°ąŗ›ąŗ²ąŗąŗ»ąŗ”ą»€ąŗ›ąŗ±ąŗ™ą»„ąŗ­ąŗ„ąŗ­ąŗ™ąŗ¢ąŗ¹ą»ˆąŗ”ąŗøąŗ”ąŗ„ąŗøą»ˆąŗ”ąŗŖąŗøąŗ”ąŗ‚ąŗ­ąŗ‡ą»œą»‰ąŗ²ąŗˆą»ąŗ‚ąŗ­ąŗ‡ąŗ—ą»ˆąŗ²ąŗ™. ą»ąŗ•ąŗ°ą»€ąŗžąŗ·ą»ˆąŗ­ąŗ‚ąŗ°ąŗ«ąŗąŗ²ąŗ ຫຼື ąŗ„ąŗ²ąŗą»€ąŗžąŗ·ą»ˆąŗ­ąŗ›ąŗ“ąŗ”ą»„ąŗ§ą»‰."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ąŗ„ąŗ§ąŗšąŗ„ąŗøąŗ”ąŗŸąŗ­ąŗ‡ą»„ąŗ”ą»‰ąŗ—ąŗøąŗą»€ąŗ§ąŗ„ąŗ²"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą»ąŗ•ąŗ°ąŗšą»ˆąŗ­ąŗ™ąŗ™ąŗµą»‰ą»€ąŗžąŗ·ą»ˆąŗ­ąŗˆąŗ±ąŗ”ąŗąŗ²ąŗ™ą»ąŗ­ąŗ±ąŗš ແຄະ ąŗąŗ²ąŗ™ąŗŖąŗ»ąŗ™ąŗ—ąŗ°ąŗ™ąŗ²ąŗ—ąŗµą»ˆąŗŖąŗ²ąŗ”ąŗ²ąŗ”ąŗŖąŗ°ą»ąŗ”ąŗ‡ą»€ąŗ›ąŗ±ąŗ™ą»ąŗšąŗšąŗŸąŗ­ąŗ‡ą»„ąŗ”ą»‰"</string>
<string name="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="4564728020654658478">"ą»ąŗ•ąŗ°ą»€ąŗžąŗ·ą»ˆąŗ­ąŗ£ąŗµąŗŖąŗ°ąŗ•ąŗ²ąŗ”ą»ąŗ­ąŗ±ąŗšąŗ™ąŗµą»‰ą»€ąŗžąŗ·ą»ˆąŗ­ąŗ”ąŗøąŗ”ąŗ”ąŗ­ąŗ‡ąŗ—ąŗµą»ˆąŗ”ąŗµąŗ‚ąŗ¶ą»‰ąŗ™"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ąŗ›ą»ˆąŗ½ąŗ™ąŗ­ąŗ±ąŗ”ąŗ•ąŗ²ąŗŖą»ˆąŗ§ąŗ™ąŗ‚ąŗ­ąŗ‡ą»ąŗ­ąŗ±ąŗšąŗ™ąŗµą»‰ą»ƒąŗ™ąŗąŗ²ąŗ™ąŗ•ąŗ±ą»‰ąŗ‡ąŗ„ą»ˆąŗ²"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ąŗ›ą»ˆąŗ½ąŗ™ąŗ­ąŗ±ąŗ”ąŗ•ąŗ²ąŗŖą»ˆąŗ§ąŗ™"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ąŗ”ąŗµąŗšąŗ±ąŗ™ąŗ«ąŗ²ąŗą»‰ąŗ­ąŗ‡ąŗ–ą»ˆąŗ²ąŗąŗ®ąŗ¹ąŗšąŗšą»?\ną»ąŗ•ąŗ°ą»€ąŗžąŗ·ą»ˆąŗ­ąŗ›ąŗ±ąŗšą»ƒą»ą»ˆ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ąŗšą»ą»ˆą»„ąŗ”ą»‰ą»ąŗą»‰ą»„ąŗ‚ąŗ”ąŗ±ąŗ™ąŗšą»?\ną»ąŗ•ąŗ°ą»€ąŗžąŗ·ą»ˆąŗ­ą»ąŗ›ąŗ‡ąŗąŗ±ąŗšąŗ„ąŗ·ąŗ™"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ąŗšą»ą»ˆąŗ”ąŗµąŗšąŗ±ąŗ™ąŗ«ąŗ²ąŗą»‰ąŗ­ąŗ‡ąŗ–ą»ˆąŗ²ąŗąŗ®ąŗ¹ąŗšąŗšą»? ą»ąŗ•ąŗ°ą»€ąŗžąŗ·ą»ˆąŗ­ā€‹ąŗ›ąŗ“ąŗ”ā€‹ą»„ąŗ§ą»‰."</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index d893fcf21d46..b98fce8f8a3f 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Perkelti ÄÆ virŔų deÅ”inėje"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Perkelti ÄÆ apačią kairėje"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Perkelti ÄÆ apačią deÅ”inėje"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"iÅ”skleisti ā€ž<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€œ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"sutraukti ā€ž<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€œ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ā€ž<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>ā€œ nustatymai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Atsisakyti burbulo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerodyti pokalbio burbule"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Supratau"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nėra naujausių burbulų"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Naujausi ir atsisakyti burbulai bus rodomi čia"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Pokalbis naudojant burbulus"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nauji pokalbiai rodomi kaip piktogramos apatiniame ekrano kampe. Palieskite piktogramą, jei norite iÅ”plėsti pokalbÄÆ, arba nuvilkite, kad atsisakytumėte."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bet kada valdyti burbulus"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Palietę čia valdykite, kurie pokalbiai ir programos gali būti rodomi burbuluose"</string>
<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="4564728020654658478">"Palieskite, kad iÅ” naujo paleistumėte Å”ią programą ir matytumėte aiÅ”kiau"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Pakeiskite Ŕios programos kraŔtinių santykį"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Keisti kraŔtinių santykį"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index a1fbcddb1e3a..11b645ef80b7 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Pārvietot augÅ”pusē pa labi"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Pārvietot apakÅ”pusē pa kreisi"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pārvietot apakÅ”pusē pa labi"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Izvērst ā€œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Sakļaut ā€œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Lietotnes <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iestatījumi"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Nerādīt burbuli"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerādīt sarunu burbuļos"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Labi"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nav nesen aizvērtu burbuļu"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Šeit būs redzami nesen rādītie burbuļi un aizvērtie burbuļi"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"TērzēŔana, izmantojot burbuļus"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Jaunas sarunas tiek parādÄ«tas kā ikonas jÅ«su ekrāna apakŔējā stÅ«rÄ«. Varat pieskarties, lai tās izvērstu, vai vilkt, lai tās noraidÄ«tu."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Pārvaldīt burbuļus jebkurā laikā"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Pieskarieties Ŕeit, lai pārvaldītu, kuras lietotnes un sarunas var rādīt burbulī"</string>
<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="4564728020654658478">"Pieskarieties, lai restartētu Å”o lietotni un uzlabotu attēlojumu."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Iestatījumos mainiet Ŕīs lietotnes malu attiecību."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mainīt malu attiecību"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 427433c7f2d4..ba2c97a4f29f 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø горе Гесно"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø Голу лево"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø Голу Гесно"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ŠæŃ€Š¾ŃˆŠøŃ€Šø <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"собери <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ŠŸŠ¾ŃŃ‚Š°Š²ŠŗŠø за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŠžŃ‚Ń„Ń€Š»Šø балонче"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ŠŠµ ŠæŃ€ŠøŠŗŠ°Š¶ŃƒŠ²Š°Ń˜ го разговорот во Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ°"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Дфатив"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŠµŠ¼Š° неоГамнешни Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ°"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ŠŠµŠ¾Š“Š°Š¼Š½ŠµŃˆŠ½ŠøŃ‚Šµ Šø отфрлените Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ° ќе се ŠæŠ¾Ń˜Š°Š²ŃƒŠ²Š°Š°Ń‚ Ń‚ŃƒŠŗŠ°"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Разговор со Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ°"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²ŠøŃ‚Šµ разговори се ŠæŠ¾Ń˜Š°Š²ŃƒŠ²Š°Š°Ń‚ како икони во Голниот агол оГ екранот. Допрете за Га ги ŠæŃ€Š¾ŃˆŠøŃ€ŠøŃ‚е или повлечете за Га ги отфрлите."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŠšŠ¾Š½Ń‚Ń€Š¾Š»ŠøŃ€Š°Ń˜Ń‚Šµ ги Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ°Ń‚а во секое време"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Допрете Ń‚ŃƒŠŗŠ° за Га оГреГите на кои апл. Šø разговори може Га се ŠæŠ¾Ń˜Š°Š²Š°Ń‚ Š±Š°Š»Š¾Š½Ń‡ŠøŃšŠ°"</string>
<string name="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="4564728020654658478">"Допрете за Га ја рестартирате Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Š°Š²Š° за поГобар приказ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ŠŸŃ€Š¾Š¼ŠµŠ½Šø го сооГносот на Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Š°Š²Š° во ā€žŠŸŠ¾ŃŃ‚Š°Š²ŠŗŠøā€œ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ŠŸŃ€Š¾Š¼ŠµŠ½ŃƒŠ²Š°ŃšŠµ на сооГносот"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼Šø со камерата?\nДопрете за Га се совпаГне повторно"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŠµ се поправи?\nДопрете за Š²Ń€Š°ŃœŠ°ŃšŠµ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŠµŠ¼Š° проблеми со камерата? Допрете за Š¾Ń‚Ń„Ń€Š»Š°ŃšŠµ."</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 5cca248e0dd4..8a95d7ef157c 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ą“®ąµą“•ą“³ą“æąµ½ ą“µą“²ą“¤ąµą“­ą“¾ą“—ą“¤ąµą“¤ąµ‡ą“•ąµą“•ąµ ą“Øąµ€ą“•ąµą“•ąµą“•"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą“šąµą“µą“Ÿąµ† ą“‡ą“Ÿą“¤ąµą“­ą“¾ą“—ą“¤ąµą“¤ąµ‡ą“•ąµą“•ąµ ą“Øąµ€ą“•ąµą“•ąµą“•"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą“šąµą“µą“Ÿąµ† ą“µą“²ą“¤ąµą“­ą“¾ą“—ą“¤ąµą“¤ąµ‡ą“•ąµą“•ąµ ą“Øąµ€ą“•ąµą“•ąµą“•"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą“µą“æą“•ą“øą“æą“Ŗąµą“Ŗą“æą“•ąµą“•ąµą“•"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą“šąµą“°ąµą“•ąµą“•ąµą“•"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą“•ąµą“°ą“®ąµ€ą“•ą“°ą“£ą“‚"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ą“¬ą“¬ą“æąµ¾ ą“”ą“æą“øąµą“®ą“æą“øąµ ą“šąµ†ą“Æąµą“Æąµ‚"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą“øą“‚ą“­ą“¾ą“·ą“£ą“‚ ą“¬ą“¬ą“æąµ¾ ą“šąµ†ą“Æąµą“Æą“°ąµą“¤ąµ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą“®ą“Øą“øąµą“øą“æą“²ą“¾ą“Æą“æ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą“…ą“Ÿąµą“¤ąµą“¤ą“æą“Ÿąµ†ą“Æąµą“³ąµą“³ ą“¬ą“¬ą“æą“³ąµą“•ąµ¾ ą“’ą“Øąµą“Øąµą“®ą“æą“²ąµą“²"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą“…ą“Ÿąµą“¤ąµą“¤ą“æą“Ÿąµ†ą“Æąµą“³ąµą“³ ą“¬ą“¬ą“æą“³ąµą“•ąµ¾, ą“”ą“æą“øąµą“®ą“æą“øąµ ą“šąµ†ą“Æąµą“¤ ą“¬ą“¬ą“æą“³ąµą“•ąµ¾ ą“Žą“Øąµą“Øą“æą“µ ą“‡ą“µą“æą“Ÿąµ† ą“¦ąµƒą“¶ąµą“Æą“®ą“¾ą“µąµą“‚"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą“¬ą“¬ą“æą“³ąµą“•ąµ¾ ą“‰ą“Ŗą“Æąµ‹ą“—ą“æą“šąµą“šąµ ą“šą“¾ą“±ąµą“±ąµ ą“šąµ†ą“Æąµą“Æąµą“•"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ą“Ŗąµą“¤ą“æą“Æ ą“øą“‚ą“­ą“¾ą“·ą“£ą“™ąµą“™ąµ¾, ą“Øą“æą“™ąµą“™ą“³ąµą“Ÿąµ† ą“øąµą“•ąµą“°ąµ€ą“Øą“æą“Øąµą“±ąµ† ą“¤ą“¾ą““ąµ† ą“®ąµ‚ą“²ą“Æą“æąµ½ ą“ą“•ąµą“•ą“£ąµą“•ą“³ą“¾ą“Æą“æ ą“¦ąµƒą“¶ąµą“Æą“®ą“¾ą“•ąµą“‚. ą“µą“æą“•ą“øą“æą“Ŗąµą“Ŗą“æą“•ąµą“•ą“¾ąµ» ą“…ą“µą“Æą“æąµ½ ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“• ą“…ą“²ąµą“²ąµ†ą“™ąµą“•ą“æąµ½ ą“”ą“æą“øąµā€Œą“®ą“æą“øąµ ą“šąµ†ą“Æąµą“Æą“¾ąµ» ą“µą“²ą“æą“šąµą“šą“æą“Ÿąµą“•."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ą“¬ą“¬ą“æą“³ąµą“•ąµ¾ ą“ą“¤ąµą“øą“®ą“Æą“¤ąµą“¤ąµą“‚ ą“Øą“æą“Æą“Øąµą“¤ąµą“°ą“æą“•ąµą“•ąµą“•"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą“ą“¤ąµŠą“•ąµą“•ąµ† ą“†ą“Ŗąµą“Ŗąµą“•ą“³ąµą“‚ ą“øą“‚ą“­ą“¾ą“·ą“£ą“™ąµą“™ą“³ąµą“‚ ą“¬ą“¬ą“æąµ¾ ą“šąµ†ą“Æąµą“Æą“¾ą“Øą“¾ą“•ąµą“®ąµ†ą“Øąµą“Øą“¤ąµ ą“®ą“¾ą“Øąµ‡ą“œąµ ą“šąµ†ą“Æąµą“Æą“¾ąµ» ą“‡ą“µą“æą“Ÿąµ† ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“•"</string>
<string name="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="4564728020654658478">"ą“®ą“æą“•ą“šąµą“š ą“•ą“¾ą““ąµā€Œą“šą“Æąµā€Œą“•ąµą“•ą“¾ą“Æą“æ ą“ˆ ą“†ą“Ŗąµą“Ŗąµ ą“±ąµ€ą“øąµā€Œą“±ąµą“±ą“¾ąµ¼ą“Ÿąµą“Ÿąµ ą“šąµ†ą“Æąµą“Æą“¾ąµ» ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“•"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą“ˆ ą“†ą“Ŗąµą“Ŗą“æą“Øąµą“±ąµ† ą“µąµ€ą“•ąµą“·ą“£ ą“…ą“Øąµą“Ŗą“¾ą“¤ą“‚, ą“•ąµą“°ą“®ąµ€ą“•ą“°ą“£ą“¤ąµą“¤ą“æąµ½ ą“®ą“¾ą“±ąµą“±ąµą“•"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą“µąµ€ą“•ąµą“·ą“£ ą“…ą“Øąµą“Ŗą“¾ą“¤ą“‚ ą“®ą“¾ą“±ąµą“±ąµą“•"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą“•ąµą“Æą“¾ą“®ą“± ą“Ŗąµą“°ą“¶ąµą“Øą“™ąµą“™ą“³ąµą“£ąµą“Ÿąµ‹?\ną“¶ą“°ą“æą“Æą“¾ą“•ąµą“•ą“¾ąµ» ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“•"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą“…ą“¤ąµ ą“Ŗą“°ą“æą“¹ą“°ą“æą“šąµą“šą“æą“²ąµą“²ąµ‡?\ną“Ŗąµą“Øą“ƒą“øąµą“„ą“¾ą“Ŗą“æą“•ąµą“•ą“¾ąµ» ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“•"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą“•ąµą“Æą“¾ą“®ą“±ą“¾ ą“Ŗąµą“°ą“¶ąµą“Øą“™ąµą“™ą“³ąµŠą“Øąµą“Øąµą“®ą“æą“²ąµą“²ąµ‡? ą“Øą“æą“°ą“øą“æą“•ąµą“•ą“¾ąµ» ą“Ÿą“¾ą“Ŗąµą“Ŗąµ ą“šąµ†ą“Æąµą“Æąµą“•."</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 72e54fcf8711..bead7fe0d1c7 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Š‘Š°Ń€ŃƒŃƒŠ½ Š“ŃŃŃˆ Š·Ó©Ó©Ń…"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Зүүн Гоош Š·Ó©Ó©Ń…"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Š‘Š°Ń€ŃƒŃƒŠ½ Гоош Š·Ó©Ó©Ń…"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-г Š“ŃŠ»Š³ŃŃ…"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>-г Ń…ŃƒŃ€Š°Š°Ń…"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-н тохиргоо"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Бөмбөлгийг хаах"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Єарилцан ŃŃ€ŠøŠ°Š³ бүү бөмбөлөг болго"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ŠžŠ¹Š»Š³Š¾Š»Š¾Š¾"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Š”Š°ŃŃ…Š½Ń‹ бөмбөлөг алга байна"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Š”Š°ŃŃ…Š½Ń‹ бөмбөлгүүГ болон ŅÆŠ» Ń…ŃŃ€ŃŠ³ŃŃŃŠ½ бөмбөлгүүГ ŃŠ½Š“ харагГана"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"БөмбөлгүүГ ашиглан чатлах"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠØŠøŠ½Ń харилцан ŃŃ€ŠøŠ°Š½ŃƒŃƒŠ“ таны Š“ŃŠ»Š³ŃŃ†ŠøŠ¹Š½ ГооГ буланГ Гүрс Ń‚ŃŠ¼Š“ŃŠ³ байГлаар харагГана. Š¢ŃŠ“Š³ŃŃŃ€ŠøŠ¹Š³ Š“ŃŠ»Š³ŃŃ…ŠøŠ¹Š½ Ń‚ŃƒŠ»Š“ Ń‚Š¾Š²ŃˆŠøŃ… ŃŃŠ²ŃŠ» хаахын Ń‚ŃƒŠ»Š“ Ń‡ŠøŃ€Š½Ń ŅÆŅÆ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"БөмбөлгүүГийг Ń…ŅÆŃŃŃŠ½ ŅÆŠµŠ“ŃŃ Ń…ŃŠ½Š°Ń…"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ямар апп болон харилцан ŃŃ€ŠøŠ°Š½ŃƒŃƒŠ“ бөмбөлгөөр харагГахыг ŃŠ½Š“ ŃƒŠ“ŠøŃ€Š“Š°Š½Š° уу"</string>
<string name="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="4564728020654658478">"ЄарагГах байГлыг ŃŠ°Š¹Š¶Ń€ŃƒŃƒŠ»Š°Ń…Ń‹Š½ Ń‚ŃƒŠ»Š“ ŃŠ½Ń аппыг Ń‚Š¾Š²ŃˆŠøŠ¶, Гахин ŃŃ…Š»ŅÆŅÆŠ»Š½Ń ŅÆŅÆ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Š­Š½Ń аппын Ń…Š°Ń€ŃŒŃ†Š°Š°Š³ ТохиргоонГ өөрчилнө ŅÆŅÆ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Š„Š°Ń€ŃŒŃ†Š°Š°Š³ өөрчлөх"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠšŠ°Š¼ŠµŃ€Ń‹Š½ асууГал гарсан уу?\nДахин Ń‚Š°Š°Ń€ŃƒŃƒŠ»Š°Ń…Ń‹Š½ Ń‚ŃƒŠ»Š“ Ń‚Š¾Š²ŃˆŠøŠ½Š¾ уу"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nŠ‘ŃƒŃ†Š°Š°Ń…Ń‹Š½ Ń‚ŃƒŠ»Š“ Ń‚Š¾Š²ŃˆŠøŠ½Š¾ уу"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠšŠ°Š¼ŠµŃ€Ń‹Š½ асууГал байхгүй юу? Єаахын Ń‚ŃƒŠ»Š“ Ń‚Š¾Š²ŃˆŠøŠ½Š¾ уу."</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index a9e6319a2110..ca0e15a0241b 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"वर ą¤‰ą¤œą¤µą„€ą¤•ą¤”ą„‡ हलवा"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¤¤ą¤³ą¤¾ą¤¶ą„€ ą¤”ą¤¾ą¤µą„€ą¤•ą¤”ą„‡ हलवा"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą¤¤ą¤³ą¤¾ą¤¶ą„€ ą¤‰ą¤œą¤µą„€ą¤•ą¤”ą„‡ हलवा"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤µą¤æą¤øą„ą¤¤ą¤¾ą¤° करा"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤•ą„‹ą¤²ą„…ą¤Ŗą„ą¤ø करा"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą¤øą„‡ą¤Ÿą¤æą¤‚ą¤—ą„ą¤œ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल औिसमिस करा"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"संभाषणाला बबल ą¤•ą¤°ą„‚ नका"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¤øą¤®ą¤œą¤²ą„‡"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą¤…ą¤²ą„€ą¤•ą¤”ą„€ą¤² ą¤•ą„‹ą¤£ą¤¤ą„‡ą¤¹ą„€ बबल ą¤Øą¤¾ą¤¹ą„€ą¤¤"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą¤…ą¤²ą„€ą¤•ą¤”ą„€ą¤² बबल आणि औिसमिस ą¤•ą„‡ą¤²ą„‡ą¤²ą„‡ बबल ą¤Æą„‡ą¤„ą„‡ ą¤¦ą¤æą¤øą¤¤ą„€ą¤²"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल ą¤µą¤¾ą¤Ŗą¤°ą„‚ą¤Ø ą¤šą„…ą¤Ÿ करा"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ą¤Øą¤µą„€ą¤Ø ą¤øą¤‚ą¤­ą¤¾ą¤·ą¤£ą„‡ ą¤¹ą„€ ą¤¤ą„ą¤®ą¤šą„ą¤Æą¤¾ ą¤øą„ą¤•ą„ą¤°ą„€ą¤Øą¤šą„ą¤Æą¤¾ ą¤¤ą¤³ą¤¾ą¤¶ą„€ ą¤…ą¤øą¤²ą„‡ą¤²ą„ą¤Æą¤¾ ą¤•ą„‹ą¤Ŗą¤±ą„ą¤Æą¤¾ą¤¤ ą¤†ą¤Æą¤•ą¤Øą¤šą„ą¤Æą¤¾ ą¤øą„ą¤µą¤°ą„‚ą¤Ŗą¤¾ą¤¤ दिसतात. ą¤¤ą„ą¤Æą¤¾ą¤‚ą¤šą¤¾ ą¤µą¤æą¤øą„ą¤¤ą¤¾ą¤° ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤Ÿą„…ą¤Ŗ करा किंवा ą¤¤ą„€ औिसमिस ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤”ą„ą¤°ą„…ą¤— करा."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"बबल ą¤•ą¤§ą„€ą¤¹ą„€ ą¤Øą¤æą¤Æą¤‚ą¤¤ą„ą¤°ą¤æą¤¤ करा"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą¤•ą„‹ą¤£ą¤¤ą„€ ą„²ą¤Ŗą„ą¤ø आणि ą¤øą¤‚ą¤­ą¤¾ą¤·ą¤£ą„‡ बबल ą¤¹ą„‹ą¤Š शकतात ą¤¹ą„‡ ą¤µą„ą¤Æą¤µą¤øą„ą¤„ą¤¾ą¤Ŗą¤æą¤¤ ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤Æą„‡ą¤„ą„‡ ą¤Ÿą„…ą¤Ŗ करा"</string>
<string name="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="4564728020654658478">"अधिक ą¤šą¤¾ą¤‚ą¤—ą¤²ą„ą¤Æą¤¾ ą¤¦ą„ƒą¤¶ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤¹ą„‡ ą¤…ā€ą„…ą¤Ŗ ą¤°ą„€ą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤•ą¤°ą¤æą¤¤ą¤¾ ą¤Ÿą„…ą¤Ŗ करा"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¤øą„‡ą¤Ÿą¤æą¤‚ą¤—ą„ą¤œ ą¤®ą¤§ą„ą¤Æą„‡ या ą„²ą¤Ŗą¤šą¤¾ ą¤†ą¤øą„ą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤¶ą„‹ बदला"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¤†ą¤øą„ą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤¶ą„‹ बदला"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¤•ą„…ą¤®ą„‡ą¤°ą¤¾ą¤¶ą„€ संबंधित ą¤•ą¤¾ą¤¹ą„€ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤†ą¤¹ą„‡ą¤¤ का?\ną¤Ŗą„ą¤Øą„ą¤¹ą¤¾ फिट ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤Ÿą„…ą¤Ŗ करा"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण ą¤ą¤¾ą¤²ą„‡ ą¤Øą¤¾ą¤¹ą„€?\ną¤°ą¤æą¤µą„ą¤¹ą¤°ą„ą¤Ÿ ą¤•ą¤°ą¤£ą„ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤•ą„ƒą¤Ŗą¤Æą¤¾ ą¤Ÿą„…ą¤Ŗ करा"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¤•ą„…ą¤®ą„‡ą¤°ą¤¾ą¤¶ą„€ संबंधित ą¤•ą„‹ą¤£ą¤¤ą„ą¤Æą¤¾ą¤¹ą„€ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤Øą¤¾ą¤¹ą„€ą¤¤ का? औिसमिस ą¤•ą¤°ą¤£ą„ā€ą¤Æą¤¾ą¤øą¤¾ą¤ ą„€ ą¤Ÿą„…ą¤Ŗ करा."</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index b47531711863..fd9fb87b918c 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Alihkan ke atas sebelah kanan"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Alihkan ke bawah sebelah kiri"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Alihkan ke bawah sebelah kanan"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"kembangkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"kuncupkan <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Tetapan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ketepikan gelembung"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan jadikan perbualan dalam bentuk gelembung"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Tiada gelembung terbaharu"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Gelembung baharu dan gelembung yang diketepikan akan dipaparkan di sini"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bersembang menggunakan gelembung"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Perbualan baharu dipaparkan sebagai ikon pada penjuru sebelah bawah skrin anda. Ketik untuk mengembangkan perbualan atau seret untuk mengetepikan perbualan."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kawal gelembung pada bila-bila masa"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Ketik di sini untuk mengurus apl dan perbualan yang boleh menggunakan gelembung"</string>
<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="4564728020654658478">"Ketik untuk memulakan semula apl ini untuk mendapatkan paparan yang lebih baik"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Tukar nisbah bidang apl ini dalam Tetapan"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tukar nisbah bidang"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index cb6a1df95b33..42bce8104a5c 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"į€Šį€¬į€˜į€€į€ŗį€‘į€­į€•į€ŗį€žį€­į€Æį€· ရွှေ့ပါ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"į€˜į€šį€ŗį€”į€±į€¬į€€į€ŗį€į€¼į€±į€žį€­į€Æį€· ရွှေ့ရန်"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"į€Šį€¬į€”į€±į€¬į€€į€ŗį€į€¼į€±į€žį€­į€Æį€· ရွှေ့ပါ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ကို ချဲ့ရန်"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ကို ချုံ့ရန်"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ဆက်တင်များ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ပူဖောင်းကွက် į€•į€šį€ŗį€›į€”į€ŗ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"į€…į€€į€¬į€øį€į€­į€Æį€„į€ŗį€øį€€į€­į€Æ ပူဖောင်းကွက် į€™į€•į€¼į€Æį€œį€Æį€•į€ŗį€•į€«į€”į€¾į€„į€·į€ŗ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"į€”į€¬į€øį€œį€Šį€ŗį€•į€¼į€®"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"į€œį€į€ŗį€į€œį€±į€¬ ပူဖောင်းကွက်များ မရှိပါ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"į€œį€į€ŗį€į€œį€±į€¬ ပူဖောင်းကွက်များနှင့် į€•į€­į€į€ŗį€œį€­į€Æį€€į€ŗį€žį€±į€¬ ပူဖောင်းကွက်များကို ဤနေရာတွင် į€™į€¼į€„į€ŗį€›į€•į€«į€™į€Šį€ŗ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"į€•į€°į€–į€±į€¬į€„į€ŗį€øį€€į€½į€€į€ŗį€žį€Æį€¶į€øį į€į€»į€į€ŗį€œį€Æį€•į€ŗį€į€¼į€„į€ŗį€ø"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"į€…į€€į€¬į€øį€į€­į€Æį€„į€ŗį€øį€”į€žį€…į€ŗį€™į€»į€¬į€øį€€į€­į€Æ į€žį€„į€·į€ŗį€–į€”į€ŗį€žį€¬į€øį€•į€¼į€„į€ŗ နောက်ခြေထောင့်တွင် į€žį€„į€ŗį€¹į€€į€±į€į€™į€»į€¬į€øį€”į€–į€¼į€…į€ŗ į€™į€¼į€„į€ŗį€›į€•į€«į€™į€Šį€ŗį‹ įŽį€„į€ŗį€øį€į€­į€Æį€·į€€į€­į€Æ ဖြန့်ရန်တို့ပါ (į€žį€­į€Æį€·) į€•į€šį€ŗį€›į€”į€ŗį€–į€­į€†į€½į€²į€•į€«į‹"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ပူဖောင်းကွက်ကို နချိန်မရွေး ထိန်းချုပ်ရန်"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"į€•į€°į€–į€±į€¬į€„į€ŗį€øį€€į€½į€€į€ŗį€žį€Æį€¶į€øį€”į€­į€Æį€„į€ŗį€žį€Šį€·į€ŗ နက်ပ်နှင့် į€…į€€į€¬į€øį€į€­į€Æį€„į€ŗį€øį€™į€»į€¬į€ø စီမံရန် ဤနေရာကို တို့ပါ"</string>
<string name="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="4564728020654658478">"į€•į€­į€Æį€€į€±į€¬į€„į€ŗį€øį€žį€±į€¬į€™į€¼į€„į€ŗį€€į€½į€„į€ŗį€øį€”į€į€½į€€į€ŗ ဤနက်ပ်ပြန်စရန် တို့ပါ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ဆက်တင်များတွင် į€¤į€”į€€į€ŗį€•į€ŗįį€”į€į€»į€­į€Æį€øį€”į€…į€¬į€øį€€į€­į€Æ ပြောင်းရန်"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"နချိုးစား ပြောင်းရန်"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"į€€į€„į€ŗį€™į€›į€¬į€•į€¼į€æį€”į€¬į€œį€¬į€øį‹\nပြင်ဆင်ရန် တို့ပါ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"į€€į€±į€¬į€„į€ŗį€øį€™į€žį€½į€¬į€øį€˜į€°į€øį€œį€¬į€øį‹\nပြန်ပြောင်းရန် တို့ပါ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ į€™į€›į€¾į€­į€˜į€°į€øį€œį€¬į€øį‹ į€•į€šį€ŗį€›į€”į€ŗ တို့ပါ။"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 6c80144fee18..cd656b846ccf 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flytt til Ćøverst til hĆøyre"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flytt til nederst til venstre"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytt til nederst til hĆøyre"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"vis <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-innstillinger"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Lukk boblen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Nylige bobler og avviste bobler vises her"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat med bobler"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nye samtaler vises som ikoner i et hjørne nede pÄ skjermen. Trykk for Ä Äpne dem, eller dra for Ä lukke dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontroller bobler nƄr som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trykk her for Ć„ administrere hvilke apper og samtaler som kan vises i bobler"</string>
<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="4564728020654658478">"Trykk for Ƅ starte denne appen pƄ nytt og fƄ en bedre visning"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Endre hĆøyde/bredde-forholdet for denne appen i innstillingene"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre hĆøyde/bredde-forholdet"</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>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index f9f580530f8b..3300bc68c084 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"सिरानमा दायाँतिर ą¤øą¤¾ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¤Ŗą„ą¤›ą¤¾ą¤°ą¤®ą¤¾ बायाँतिर ą¤øą¤¾ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą¤Ŗą„ą¤›ą¤¾ą¤°ą¤®ą¤¾ दायाँतिर ą¤øą¤¾ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤ą¤•ą„ą¤øą„ą¤Ŗą¤¾ą¤Øą„ą¤” ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¤•ą„‹ą¤²ą„ą¤Æą¤¾ą¤Ŗą„ą¤ø ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का ą¤øą„‡ą¤Ÿą¤æą¤™ą¤¹ą¤°ą„‚"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल ą¤–ą¤¾ą¤°ą„‡ą¤œ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą¤µą¤¾ą¤°ą„ą¤¤ą¤¾ą¤²ą¤¾ą¤Ŗ ą¤¬ą¤¬ą¤²ą¤•ą„‹ ą¤°ą„‚ą¤Ŗą¤®ą¤¾ ą¤Øą¤¦ą„‡ą¤–ą¤¾ą¤‡ą¤Æą„‹ą¤øą„"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¤¬ą„ą¤ą„‡ą¤"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą¤¹ą¤¾ą¤²ą„ˆą¤•ą¤¾ ą¤¬ą¤¬ą¤²ą¤¹ą¤°ą„‚ ą¤›ą„ˆą¤Øą¤Øą„"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą¤¹ą¤¾ą¤²ą„ˆą¤•ą¤¾ बबल र ą¤–ą¤¾ą¤°ą„‡ą¤œ ą¤—ą¤°ą¤æą¤ą¤•ą¤¾ ą¤¬ą¤¬ą¤²ą¤¹ą¤°ą„‚ यहाँ ą¤¦ą„‡ą¤–ą¤æą¤Øą„‡ ą¤›ą¤Øą„"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल ą¤Ŗą„ą¤°ą¤Æą„‹ą¤— ą¤—ą¤°ą„€ ą¤•ą„ą¤°ą¤¾ą¤•ą¤¾ą¤Øą„€ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नयाँ ą¤µą¤¾ą¤°ą„ą¤¤ą¤¾ą¤²ą¤¾ą¤Ŗą¤¹ą¤°ą„‚ ą¤¤ą¤Ŗą¤¾ą¤ˆą¤‚ą¤•ą„‹ ą¤”ą¤æą¤­ą¤¾ą¤‡ą¤øą¤•ą„‹ ą¤øą„ą¤•ą„ą¤°ą¤æą¤Øą¤•ą„‹ ą¤Ŗą„ą¤›ą¤¾ą¤°ą¤•ą„‹ ą¤•ą„ą¤Øą¤¾ą¤®ą¤¾ आइकनका ą¤°ą„‚ą¤Ŗą¤®ą¤¾ ą¤¦ą„‡ą¤–ą¤æą¤Øą„ą¤›ą¤Øą„ą„¤ ą¤¤ą„€ आइकन ą¤ ą„ą¤²ą„‹ बनाउन ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„ वा ą¤¤ą„€ आइकन ą¤¹ą¤Ÿą¤¾ą¤‰ą¤Ø ą¤”ą„ą¤°ą„ą¤Æą¤¾ą¤— ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„ą„¤"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ą¤œą„ą¤Øą¤øą„ą¤•ą„ˆ ą¤¬ą„‡ą¤²ą¤¾ ą¤¬ą¤¬ą¤²ą¤øą¤®ą„ą¤¬ą¤Øą„ą¤§ą„€ ą¤øą„ą¤µą¤æą¤§ą¤¾ ą¤Øą¤æą¤Æą¤Øą„ą¤¤ą„ą¤°ą¤£ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą¤•ą„ą¤Ø ą¤ą¤Ŗ र ą¤•ą„ą¤°ą¤¾ą¤•ą¤¾ą¤Øą„€ बबल ą¤Ŗą„ą¤°ą¤Æą„‹ą¤— ą¤—ą¤°ą„ą¤Ø ą¤øą¤•ą„ą¤›ą¤Øą„ ą¤­ą¤Øą„ą¤Øą„‡ ą¤•ą„ą¤°ą¤¾ą¤•ą„‹ ą¤µą„ą¤Æą¤µą¤øą„ą¤„ą¤¾ą¤Ŗą¤Ø ą¤—ą¤°ą„ą¤Ø यहाँ ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="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="4564728020654658478">"ą¤…ą¤ ą¤°ą¤¾ą¤®ą„ą¤°ą„‹ ą¤­ą„ą¤Æą„‚ ą¤Ŗą„ą¤°ą¤¾ą¤Ŗą„ą¤¤ ą¤—ą¤°ą„ą¤Øą¤•ą¤¾ लागि ą¤Æą„‹ ą¤ą¤Ŗ ą¤°ą¤æą¤øą„ą¤Ÿą¤¾ą¤°ą„ą¤Ÿ ą¤—ą¤°ą„ą¤Ø ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¤øą„‡ą¤Ÿą¤æą¤™ą¤®ą¤¾ ą¤—ą¤ˆ ą¤Æą„‹ ą¤ą¤Ŗą¤•ą„‹ ą¤ą¤øą„ą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤øą¤æą¤Æą„‹ ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¤ą¤øą„ą¤Ŗą„‡ą¤•ą„ą¤Ÿ ą¤°ą„‡ą¤øą¤æą¤Æą„‹ ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¤•ą„ą¤Æą¤¾ą¤®ą„‡ą¤°ą¤¾ą¤øą¤®ą„ą¤¬ą¤Øą„ą¤§ą„€ ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤¦ą„‡ą¤–ą¤æą¤Æą„‹?\ną¤øą¤®ą¤øą„ą¤Æą¤¾ हल ą¤—ą¤°ą„ą¤Ø ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¤øą¤®ą¤øą„ą¤Æą¤¾ हल ą¤­ą¤ą¤Ø?\ną¤Ŗą¤¹ą¤æą¤²ą„‡ą¤•ą„‹ ą¤œą¤øą„ą¤¤ą„ˆ बनाउन ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¤•ą„ą¤Æą¤¾ą¤®ą„‡ą¤°ą¤¾ą¤øą¤®ą„ą¤¬ą¤Øą„ą¤§ą„€ ą¤•ą„ą¤Øą„ˆ पनि ą¤øą¤®ą¤øą„ą¤Æą¤¾ ą¤›ą„ˆą¤Ø? ą¤–ą¤¾ą¤°ą„‡ą¤œ ą¤—ą¤°ą„ą¤Ø ą¤Ÿą„ą¤Æą¤¾ą¤Ŗ ą¤—ą¤°ą„ą¤Øą„ą¤¹ą„‹ą¤øą„ą„¤"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 3064cccba7cc..0d174049dc15 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Naar rechtsboven verplaatsen"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Naar linksonder verplaatsen"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> uitvouwen"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> samenvouwen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen recente bubbels"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatten met bubbels"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nieuwe gesprekken verschijnen als iconen in een benedenhoek van je scherm. Tik om ze uit te vouwen of sleep om ze te sluiten."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string>
<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="4564728020654658478">"Tik om deze app opnieuw op te starten voor een betere weergave"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Wijzig de beeldverhouding van deze app in Instellingen"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Beeldverhouding wijzigen"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index e4c70537c452..916d8638436c 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -26,7 +26,7 @@
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"ą¬ą¬¹ą¬æ ą¬¬ą­ˆą¬¶ą¬æą¬·ą­ą¬Ÿą­ą­Ÿ <xliff:g id="NAME">%s</xliff:g> ą¬¬ą­ą­Ÿą¬¬ą¬¹ą¬¾ą¬° ନକରିବାକୁ ଯଦି ଆପଣ ą¬šą¬¾ą¬¹ą¬¾ą¬ą¬Øą­ą¬¤ą¬æ, ą¬øą­‡ą¬Ÿą¬æą¬™ą­ą¬— ଖୋଲିବାକୁ ą¬Ÿą¬¾ą¬Ŗą­ā€ ą¬•ą¬°ą¬Øą­ą¬¤ą­ ą¬ą¬¬ą¬‚ ą¬ą¬¹ą¬¾ ą¬…ą¬«ą­ā€ ą¬•ą¬°ą¬æą¬¦ą¬æą¬…ą¬Øą­ą¬¤ą­ą„¤"</string>
<string name="pip_play" msgid="3496151081459417097">"ą¬Ŗą­ą¬²ą­‡ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
- <string name="pip_pause" msgid="690688849510295232">"ą¬Ŗą¬œą­ā€ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="pip_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>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ଉପର-ଔାହାଣକୁ ą¬Øą¬æą¬…ą¬Øą­ą¬¤ą­"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ତଳ ବାମକୁ ą¬Øą¬æą¬…ą¬Øą­ą¬¤ą­"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଔାହାଣକୁ ą¬Øą¬æą¬…ą¬Øą­ą¬¤ą­"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¬¬ą¬æą¬øą­ą¬¤ą¬¾ą¬° ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¬øą¬™ą­ą¬•ą­ą¬šą¬æą¬¤ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą¬øą­‡ą¬Ÿą¬æą¬‚ą¬øą­"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ą¬¬ą¬¬ą¬²ą­ ą¬–ą¬¾ą¬°ą¬œ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą¬¬ą¬¾ą¬°ą­ą¬¤ą­ą¬¤ą¬¾ą¬³ą¬¾ą¬Ŗą¬•ą­ ą¬¬ą¬¬ą¬²ą­ ą¬•ą¬°ą¬Øą­ą¬¤ą­ ନାହିଁ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¬¬ą­ą¬ą¬æą¬—ą¬²ą¬æ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą¬¬ą¬°ą­ą¬¤ą­ą¬¤ą¬®ą¬¾ą¬Ø ą¬•ą­Œą¬£ą¬øą¬æ ą¬¬ą¬¬ą¬²ą­ ନାହିଁ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą¬¬ą¬°ą­ą¬¤ą­ą¬¤ą¬®ą¬¾ą¬Øą¬° ą¬ą¬¬ą¬‚ ą¬–ą¬¾ą¬°ą¬œ କରାଯାଇ଄ିବା ବବଲଗୁଔ଼ିକ ą¬ą¬ ą¬¾ą¬°ą­‡ ଦେଖାଯିବ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ବବଲଗୁଔ଼ିକୁ ą¬¬ą­ą­Ÿą¬¬ą¬¹ą¬¾ą¬° କରି ą¬šą¬¾ą¬Ÿą­ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ନୂଆ ą¬¬ą¬¾ą¬°ą­ą¬¤ą­ą¬¤ą¬¾ą¬³ą¬¾ą¬Ŗą¬—ą­ą¬”ą¬¼ą¬æą¬• ą¬†ą¬Ŗą¬£ą¬™ą­ą¬• ą¬øą­ą¬•ą­ą¬°ą¬æą¬Øą¬° ą¬ą¬• ą¬Øą¬æą¬®ą­ą¬Ø କୋଣରେ ଆଇକନଗୁଔ଼ିକ ଭାବେ ą¬¦ą­‡ą¬–ą¬¾ą¬Æą¬¾ą¬ą„¤ ସେଗୁଔ଼ିକୁ ą¬¬ą¬æą¬øą­ą¬¤ą¬¾ą¬° କରିବା ପାଇଁ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­ ą¬•ą¬æą¬®ą­ą¬¬ą¬¾ ସେଗୁଔ଼ିକୁ ą¬–ą¬¾ą¬°ą¬œ କରିବା ପାଇଁ ą¬”ą­ą¬°ą¬¾ą¬— ą¬•ą¬°ą¬Øą­ą¬¤ą­ą„¤"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ଯେ ą¬•ą­Œą¬£ą¬øą¬æ ą¬øą¬®ą­Ÿą¬°ą­‡ ବବଲଗୁଔ଼ିକ ą¬Øą¬æą­Ÿą¬Øą­ą¬¤ą­ą¬°ą¬£ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"କେଉଁ ą¬†ą¬Ŗą­ą¬ø ଓ ą¬¬ą¬¾ą¬°ą­ą¬¤ą­ą¬¤ą¬¾ą¬³ą¬¾ą¬Ŗą¬—ą­ą¬”ą¬¼ą¬æą¬• ବବଲ ହୋଇପାରିବ ତାହା ପରିଚାଳନା କରିବାକୁ ą¬ą¬ ą¬¾ą¬°ą­‡ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="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="4564728020654658478">"ą¬ą¬• ଆହୁରି ଭଲ ą¬­ą­ą­Ÿą­ ପାଇଁ ą¬ą¬¹ą¬æ ଆପ ą¬°ą¬æą¬·ą­ą¬Ÿą¬¾ą¬°ą­ą¬Ÿ କରିବାକୁ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¬øą­‡ą¬Ÿą¬æą¬‚ą¬øą¬°ą­‡ ą¬ą¬¹ą¬æ ଆପର ą¬šą¬‰ą¬”ą¬¼ą¬¾ ଓ ą¬‰ą¬šą­ą¬šą¬¤ą¬¾ą¬° ଅନୁପାତ ą¬Ŗą¬°ą¬æą¬¬ą¬°ą­ą¬¤ą­ą¬¤ą¬Ø ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¬šą¬‰ą¬”ą¬¼ą¬¾ ଓ ą¬‰ą¬šą­ą¬šą¬¤ą¬¾ą¬° ଅନୁପାତ ą¬Ŗą¬°ą¬æą¬¬ą¬°ą­ą¬¤ą­ą¬¤ą¬Ø ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¬•ą­ą­Ÿą¬¾ą¬®ą­‡ą¬°ą¬¾ą¬°ą­‡ ą¬øą¬®ą¬øą­ą­Ÿą¬¾ ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¬ą¬¹ą¬¾ą¬° ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¬•ą­ą­Ÿą¬¾ą¬®ą­‡ą¬°ą¬¾ą¬°ą­‡ କିଛି ą¬øą¬®ą¬øą­ą­Ÿą¬¾ ନାହିଁ? ą¬–ą¬¾ą¬°ą¬œ କରିବାକୁ ଟାପ ą¬•ą¬°ą¬Øą­ą¬¤ą­ą„¤"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index d9f7f340a889..5c936e069f9e 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ਉੱਪਰ ਵੱਲ ąØøą©±ąØœą©‡ ąØ²ąØæąØœąØ¾ąØ“"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ąØ²ąØæąØœąØ¾ąØ“"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ਹੇਠਾਂ ਵੱਲ ąØøą©±ąØœą©‡ ąØ²ąØæąØœąØ¾ąØ“"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ਨੂੰ ąØøąØ®ą©‡ąØŸą©‹"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ąØøą©ˆąØŸąØæą©°ąØ—ąØ¾ąØ‚"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ਬਬਲ ਨੂੰ ąØ–ąØ¾ąØ°ąØœ ਕਰੋ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ਗੱਲਬਾਤ \'ਤੇ ਬਬਲ ਨਾ ਲਾਓ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ąØøąØ®ąØ ਲਿਆ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ąØ•ą©‹ąØˆ ਹਾਲੀਆ ਬਬਲ ਨਹੀਂ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ਹਾਲੀਆ ਬਬਲ ਅਤੇ ąØ–ąØ¾ąØ°ąØœ ਕੀਤੇ ਬਬਲ ਇੱ਄ੇ ਦਿਸਣਗੇ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ਬਬਲ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਚੈਟ ਕਰੋ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ਨਵੀਂਆਂ ਗੱਲਾਂਬਾਤਾਂ ਤੁਹਾਔੀ ąØøąØ•ą©ąØ°ą©€ąØØ ਦੇ ਹੇਠਲੇ ਕੋਨੇ ਵਿੱਚ ąØŖą©ąØ°ąØ¤ą©€ąØ•ąØ¾ąØ‚ ਦੇ ਰੂਪ ਵਿੱਚ ਦਿਖਦੀਆਂ ąØ¹ąØØą„¤ ąØ‰ąØØą©ąØ¹ąØ¾ąØ‚ ਦਾ ਵਿਸਤਾਰ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ ąØœąØ¾ąØ‚ ąØ‰ąØØą©ąØ¹ąØ¾ąØ‚ ਨੂੰ ąØ–ąØ¾ąØ°ąØœ ਕਰਨ ਲਈ ąØ˜ąØøą©€ąØŸą©‹ą„¤"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ਬਬਲ ਦੀ ਸੁਵਿਧਾ ਨੂੰ ਕਿਸੇ ਵੀ ਵੇਲੇ ąØ•ą©°ąØŸąØ°ą©‹ąØ² ਕਰੋ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ਇਹ ąØŖą©ąØ°ąØ¬ą©°ąØ§ąØØ ਕਰਨ ਲਈ ਇੱ਄ੇ ਟੈਪ ਕਰੋ ਕਿ ąØ•ąØæąØ¹ą©œą©€ąØ†ąØ‚ ਐਪਾਂ ਅਤੇ ਗੱਲਾਂਬਾਤਾਂ ਬਬਲ ਹੋ ਸਕਦੀਆਂ ਹਨ"</string>
<string name="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="4564728020654658478">"ਬਿਹਤਰ ąØ¦ą©ąØ°ąØæąØøąØ¼ ਵਾਸਤੇ ਇਸ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ąØøą©ˆąØŸąØæą©°ąØ—ąØ¾ąØ‚ ਵਿੱਚ ਜਾ ਕੇ ਇਸ ਐਪ ਦੇ ਆਕਾਰ ਅਨੁਪਾਤ ਨੂੰ ਬਦਲੋ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ąØ•ą©ˆąØ®ąØ°ą©‡ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ąØ¹ą©‹ąØˆ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ąØ•ą©ˆąØ®ąØ°ą©‡ ਸੰਬੰਧੀ ąØ•ą©‹ąØˆ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ąØ–ąØ¾ąØ°ąØœ ਕਰਨ ਲਈ ਟੈਪ ąØ•ąØ°ą©‹ą„¤"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 0699f5df99ab..abdb5f7eb7ac 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Przenieś wĀ prawy górny róg"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Przenieś wĀ lewy dolny róg"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Przenieś w prawy dolny róg"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozwiń dymek <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"zwiń dymek <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ustawienia"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zamknij dymek"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nie wyświetlaj rozmowy jako dymka"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Brak ostatnich dymków"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tutaj będą pojawiać się ostatnie iĀ odrzucone dymki"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Czatuj, korzystając zĀ dymków"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nowe rozmowy pojawiają się jako ikony wĀ dolnym rogu ekranu. Kliknij, aby je rozwinąć, lub przeciągnij, aby je zamknąć."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Zarządzaj dymkami, kiedy chcesz"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Kliknij tutaj, aby zarządzać wyświetlaniem aplikacji iĀ rozmów jako dymków"</string>
<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="4564728020654658478">"Kliknij w celu zrestartowania aplikacji, aby lepiej się wyświetlała."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmień proporcje obrazu aplikacji wĀ Ustawieniach"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmień proporcje obrazu"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index eea9be26f432..bb68b28075bb 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover para canto superior direito"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover para canto inferior esquerdo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"abrir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"fechar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ConfiguraƧƵes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse usando balƵes"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novas conversas aparecem como Ć­cones no canto inferior da tela. Toque neles para abrir ou arraste para dispensar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balƵes a qualquer momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balƵes"</string>
<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="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas ConfiguraƧƵes"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index ed0cdb61dacf..2c03c543d1dc 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"Menu"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menu de ecrã no ecrã"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"A app <xliff:g id="NAME">%s</xliff:g> estÔ no modo de ecrã no ecrã"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"Se não pretende que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"Se não quer que a app <xliff:g id="NAME">%s</xliff:g> utilize esta funcionalidade, toque para abrir as definições e desative-a."</string>
<string name="pip_play" msgid="3496151081459417097">"Reproduzir"</string>
<string name="pip_pause" msgid="690688849510295232">"Pausar"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"Mudar para o seguinte"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover parte superior direita"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover p/ parte infer. esquerda"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"expandir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"reduzir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"DefiniƧƵes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não apresentar a conversa em balões"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e ignorados vão aparecer aqui."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse no chat atravƩs de balƵes"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"As novas conversas aparecem como ícones no canto inferior do ecrã. Toque para as expandir ou arraste para as ignorar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balƵes em qualquer altura"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerir que apps e conversas podem aparecer em balƵes"</string>
<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="4564728020654658478">"Toque para reiniciar esta app e ficar com uma melhor visão"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Altere o formato desta app nas DefiniƧƵes"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Altere o formato"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index eea9be26f432..bb68b28075bb 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Mover para canto superior direito"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Mover para canto inferior esquerdo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"abrir <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"fechar <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ConfiguraƧƵes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nenhum balão recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Os balões recentes e dispensados aparecerão aqui"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Converse usando balƵes"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novas conversas aparecem como Ć­cones no canto inferior da tela. Toque neles para abrir ou arraste para dispensar."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controle os balƵes a qualquer momento"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Toque aqui para gerenciar quais apps e conversas podem aparecer em balƵes"</string>
<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="4564728020654658478">"Toque para reiniciar o app e atualizar a visualização"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Mude o tamanho da janela deste app nas ConfiguraƧƵes"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Mudar a proporção"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 8a64b1686543..579046b3a45f 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -66,6 +66,8 @@
<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_announce_expand" msgid="5388792092888203776">"extinde <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"restrânge <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</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>
@@ -76,10 +78,16 @@
<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="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chat cu baloane"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Conversațiile noi apar ca pictograme Ć®n colțul de jos al ecranului. Atinge pentru a le extinde sau trage pentru a le Ć®nchide."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlează baloanele oricând"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Atinge aici pentru a gestiona aplicațiile și conversațiile care pot apărea Ć®n balon"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
- <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Atinge ca să repornești aplicația pentru o vizualizare mai bună"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Schimbă raportul de dimensiuni al aplicației din Setări"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Schimbă raportul de dimensiuni"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index a7db44dda901..a8a914cbf9fc 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŠµŃ€ŠµŠ½ŠµŃŃ‚Šø в правый верхний угол"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŠµŃ€ŠµŠ½ŠµŃŃ‚Šø в левый нижний угол"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŠµŃ€ŠµŠ½ŠµŃŃ‚Šø в правый нижний угол"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Š Š°Š·Š²ŠµŃ€Š½ŃƒŃ‚ŃŒ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Š”Š²ŠµŃ€Š½ŃƒŃ‚ŃŒ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Š”ŠŗŃ€Ń‹Ń‚ŃŒ Š²ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠ¹ чат"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ŠŠµ ŠæŠ¾ŠŗŠ°Š·Ń‹Š²Š°Ń‚ŃŒ Š²ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠ¹ чат Š“Š»Ń разговора"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ŠžŠš"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŠµŃ‚ неГавних Š²ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŃ… чатов"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Š—Š“ŠµŃŃŒ Š±ŃƒŠ“ŃƒŃ‚ ŠæŠ¾ŃŠ²Š»ŃŃ‚ŃŒŃŃ неГавние Šø скрытые Š²ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠµ чаты."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Š’ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠµ чаты"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²Ń‹Šµ чаты ŠæŠ¾ŃŠ²Š»ŃŃŽŃ‚ŃŃ в виГе значков в нижней части ŃŠŗŃ€Š°Š½Š°. ŠšŠ¾ŃŠ½ŠøŃ‚ŠµŃŃŒ ŠøŃ…, чтобы Ń€Š°Š·Š²ŠµŃ€Š½ŃƒŃ‚ŃŒ. ŠŸŠµŃ€ŠµŃ‚Š°Ń‰ŠøŃ‚Šµ ŠøŃ…, если хотите Š·Š°ŠŗŃ€Ń‹Ń‚ŃŒ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Š’ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠµ чаты"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Укажите ŠæŃ€ŠøŠ»Š¾Š¶ŠµŠ½ŠøŃ Šø разговоры, Š“Š»Ń которых Ń€Š°Š·Ń€ŠµŃˆŠµŠ½Ń‹ Š²ŃŠæŠ»Ń‹Š²Š°ŃŽŃ‰ŠøŠµ чаты."</string>
<string name="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="4564728020654658478">"ŠŠ°Š¶Š¼ŠøŃ‚Šµ, чтобы ŠæŠµŃ€ŠµŠ·Š°ŠæŃƒŃŃ‚ŠøŃ‚ŃŒ приложение Šø Š¾ŠæŃ‚ŠøŠ¼ŠøŠ·ŠøŃ€Š¾Š²Š°Ń‚ŃŒ размер"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Š˜Š·Š¼ŠµŠ½ŠøŃ‚ŃŒ ŃŠ¾Š¾Ń‚Š½Š¾ŃˆŠµŠ½ŠøŠµ сторон ŠæŃ€ŠøŠ»Š¾Š¶ŠµŠ½ŠøŃ в настройках"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Š˜Š·Š¼ŠµŠ½ŠøŃ‚ŃŒ ŃŠ¾Š¾Ń‚Š½Š¾ŃˆŠµŠ½ŠøŠµ сторон"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼Ń‹ с камерой?\nŠŠ°Š¶Š¼ŠøŃ‚Šµ, чтобы ŠøŃŠæŃ€Š°Š²ŠøŃ‚ŃŒ."</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŠµ помогло?\nŠŠ°Š¶Š¼ŠøŃ‚Šµ, чтобы Š¾Ń‚Š¼ŠµŠ½ŠøŃ‚ŃŒ ŠøŠ·Š¼ŠµŠ½ŠµŠ½ŠøŃ."</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŠµŃ‚ проблем с камерой? ŠŠ°Š¶Š¼ŠøŃ‚Šµ, чтобы Š·Š°ŠŗŃ€Ń‹Ń‚ŃŒ."</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 4153ce2bcf1f..968bc2c3b862 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ඉහළ ą¶Æą¶šą·”ą¶«ą¶§ ą¶œą·™ą¶± යන්න"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ą¶“ą·„ą·… වමට ą¶œą·™ą¶± යන්න"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ą¶“ą·„ą·… ą¶Æą¶šą·”ą¶«ą¶§ ą¶œą·™ą¶± යන්න"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą¶Æą·’ą¶œ ą·„ą¶»ą·’ą¶±ą·Šą¶±"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą·„ą¶šą·”ą·…ą¶±ą·Šą¶±"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą·ƒą·ą¶šą·ƒą·“ą¶øą·Š"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"බුබුලු ඉවත ලන්න"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą·ƒą¶‚ą·€ą·ą¶Æą¶ŗ බුබුලු නොදමන්න"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą¶­ą·šą¶»ą·”ą¶«ą·"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą¶øą·‘ą¶­ බුබුලු නැත"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą¶øą·‘ą¶­ බුබුලු ą·ƒą·„ ඉවත ලූ බුබුලු මෙහි ą¶Æą·’ą·ƒą·Š වනු ඇත"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"බුබුලු ą¶·ą·ą·€ą·’ą¶­ą¶ŗą·™ą¶±ą·Š ą¶šą¶­ą·ą¶¶ą·„ කරන්න"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"නව ą·ƒą¶‚ą·€ą·ą¶Æ ą¶”ą¶¶ą·š තිරයෙහි ą¶“ą·„ą·… ą¶šą·™ą·…ą·€ą¶»ą·š ą¶±ą·’ą¶»ą·–ą¶“ą¶š ą¶½ą·™ą·ƒ ą¶Æą·’ą·ƒą·Š ą·€ą·š. ą¶’ą·€ą· ą¶“ą·”ą·…ą·”ą¶½ą·Š ą¶šą·’ą¶»ą·“ą¶øą¶§ ą¶­ą¶§ą·Šą¶§ą·” කරන්න ą·„ą· ą¶’ą·€ą· ඉවත දැමීමට ą¶…ą¶Æą·’ą¶±ą·Šą¶±."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ඕනෑම ą·€ą·šą¶½ą·ą·€ą¶š බුබුලු ą¶“ą·ą¶½ą¶±ą¶ŗ කරන්න"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"බුබුලු ą¶šą·… ą·„ą·ą¶šą·’ ą¶ŗą·™ą¶Æą·”ą¶øą·Š ą·ƒą·„ ą·ƒą¶‚ą·€ą·ą¶Æ ą¶šą·…ą¶øą¶±ą·ą¶šą¶»ą¶«ą¶ŗ ą¶šą·’ą¶»ą·“ą¶øą¶§ මෙහි ą¶­ą¶§ą·Šą¶§ą·” කරන්න"</string>
<string name="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="4564728020654658478">"ą·€ą¶©ą· ą·„ą·œą¶³ ą¶Æą·ƒą·”ą¶±ą¶šą·Š ą·ƒą¶³ą·„ą· මෙම යෙදුම ą¶ŗą·…ą·’ ඇරඹීමට ą¶­ą¶§ą·Šą¶§ą·” කරන්න"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą·ƒą·ą¶šą·ƒą·“ą¶øą·Š තුළ මෙම ą¶ŗą·™ą¶Æą·”ą¶øą·š දර්ශන ą¶…ą¶±ą·”ą¶“ą·ą¶­ą¶ŗ ą·€ą·™ą¶±ą·ƒą·Š කරන්න"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"දර්ශන ą¶…ą¶±ą·”ą¶“ą·ą¶­ą¶ŗ ą·€ą·™ą¶±ą·ƒą·Š කරන්න"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ą¶šą·ą¶øą¶»ą· ą¶œą·ą¶§ą¶½ą·”ą¶Æ?\ną¶ŗą·…ą·’ ą·ƒą·€ą·’ ą¶šą·’ą¶»ą·“ą¶øą¶§ ą¶­ą¶§ą·Šą¶§ą·” කරන්න"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą¶‘ą¶ŗ ą·€ą·’ą·ƒą¶³ą·”ą·€ą·š නැතිද?\ną¶“ą·Šā€ą¶»ą¶­ą·’ą·€ą¶»ą·Šą¶­ą¶±ą¶ŗ ą¶šą·’ą¶»ą·“ą¶øą¶§ ą¶­ą¶§ą·Šą¶§ą·” කරන්න"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ą¶šą·ą¶øą¶»ą· ą¶œą·ą¶§ą¶½ą·” ą¶±ą·œą¶øą·ą¶­ą·’ą¶Æ? ඉවත දැමීමට ą¶­ą¶§ą·Šą¶§ą·” කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4e38943662ea..303d81b17568 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"PresunĆŗÅ„ doprava nahor"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Presunúń doľava nadol"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"PresunĆŗÅ„ doprava nadol"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"rozbaliń <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"zbaliń <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavenia aplikƔcie <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavrień bublinu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovań konverzÔciu ako bublinu"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Dobre"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Žiadne nedÔvne bubliny"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tu sa budú zobrazovań nedÔvne a zavreté bubliny"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Čet pomocou bublín"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nové konverzÔcie sa zobrazujú ako ikony v dolnom rohu obrazovky. Klepnutím ich rozbalíte a presunutím zavriete."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"OvlÔdajte bubliny kedykoľvek"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Klepnite tu a spravujte, ktoré aplikÔcie a konverzÔcie mÓžu ovlÔdań bubliny"</string>
<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="4564728020654658478">"Ak chcete zlepŔiń zobrazenie, klepnutím túto aplikÔciu reŔtartujte"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Zmeniń pomer strÔn tejto aplikÔcie v Nastaveniach"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Zmeniń pomer strÔn"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index b0e67a771653..6178a1a9a489 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Premakni zgoraj desno"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Premakni spodaj levo"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premakni spodaj desno"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"razŔiritev oblačka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"strnitev oblačka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavitve za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Opusti oblaček"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Pogovora ne prikaži v oblačku"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"V redu"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ni nedavnih oblačkov"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Tukaj bodo prikazani tako nedavni kot tudi opuŔčeni oblački"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Klepet z oblački"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Novi pogovori so prikazani kot ikone v enem od spodnjih kotov zaslona. Z dotikom pogovore razŔirite, z vlečenjem jih opustite."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljanje oblačkov"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dotaknite se tukaj za upravljanje aplikacij in pogovorov, ki so lahko prikazani v oblačkih"</string>
<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="4564728020654658478">"Če želite boljÅ”i prikaz, se dotaknite za vnovični zagon te aplikacije."</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Razmerje stranic te aplikacije spremenite v nastavitvah."</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Sprememba razmerja stranic"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 29bfb9268f47..afccac292ebe 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Lƫviz lart djathtas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Zhvendos poshtƫ majtas"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Lƫvize poshtƫ djathtas"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"zgjero <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"palos <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cilƫsimet e <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Hiqe flluskƫn"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Mos e vendos bisedƫn nƫ flluskƫ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"E kuptova"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nuk ka flluska tƫ fundit"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Flluskat e fundit dhe flluskat e hequra do tƫ shfaqen kƫtu"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bisedo duke pƫrdorur flluskat"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Bisedat e reja shfaqen si ikona nƫ kƫndin e poshtƫm tƫ ekranit tƫnd. Trokit pƫr t\'i zgjeruar ose zvarrit pƫr t\'i hequr ato."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrollo flluskat nƫ Ƨdo moment"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Trokit kƫtu pƫr tƫ menaxhuar aplikacionet e bisedat qƫ do tƫ shfaqen nƫ flluska"</string>
<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="4564728020654658478">"Trokit pƫr ta rinisur kƫtƫ aplikacion pƫr njƫ pamje mƫ tƫ mirƫ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ndrysho raportin e pamjes sƫ kƫtij aplikacioni te \"Cilƫsimet\""</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ndrysho raportin e pamjes"</string>
<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>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje pƫr mƫ shumƫ informacion."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Rinis pƫr njƫ pamje mƫ tƫ mirƫ?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund tƫ rinisƫsh aplikacionin nƫ mƫnyrƫ qƫ tƫ duket mƫ mirƫ nƫ ekranin tƫnd, por mund tƫ humbƫsh progresin ose Ƨdo ndryshim tƫ paruajtur"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund ta rinisƫsh aplikacionin nƫ mƫnyrƫ qƫ tƫ duket mƫ mirƫ nƫ ekranin tƫnd, por mund ta humbasƫsh progresin ose Ƨdo ndryshim tƫ paruajtur"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq pƫrsƫri"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 307efc9d6a2e..0e70a47f3fcb 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø горе Гесно"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø Голе лево"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŃ€ŠµŠ¼ŠµŃŃ‚Šø Голе Гесно"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ŠæŃ€Š¾ŃˆŠøŃ€ŠøŃ‚Šµ облачић <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ŃŠŗŃƒŠæŠøŃ‚Šµ облачић <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ПоГешавања за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ŠžŠ“Š±Š°Ń†Šø облачић"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ŠŠµ користи облачиће за ŠŗŠ¾Š½Š²ŠµŃ€Š·Š°Ń†ŠøŃ˜Ńƒ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Важи"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŠµŠ¼Š° неГавних облачића"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ŠžŠ²Š“Šµ се ŠæŃ€ŠøŠŗŠ°Š·ŃƒŃ˜Ńƒ неГавни Šø оГбачени облачићи"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Š‹Š°ŃŠŗŠ°Ń˜Ń‚Šµ у облачићима"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²Šµ ŠŗŠ¾Š½Š²ŠµŃ€Š·Š°Ń†ŠøŃ˜Šµ се ŠæŠ¾Ń˜Š°Š²Ń™ŃƒŃ˜Ńƒ као иконе у Гоњем углу екрана. ДоГирните Га бисте ŠøŃ… ŠæŃ€Š¾ŃˆŠøŃ€ŠøŠ»Šø или ŠæŃ€ŠµŠ²ŃƒŃ†ŠøŃ‚е Га бисте ŠøŃ… оГбацили."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŠšŠ¾Š½Ń‚Ń€Š¾Š»ŠøŃˆŠøŃ‚Šµ облачиће у сваком Ń‚Ń€ŠµŠ½ŃƒŃ‚ŠŗŃƒ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ДоГирните овГе Šø оГреГите које Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Šµ Šø ŠŗŠ¾Š½Š²ŠµŃ€Š·Š°Ń†ŠøŃ˜Šµ могу Га имају облачић"</string>
<string name="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="4564728020654658478">"ДоГирните Га бисте рестартовали ову Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Ńƒ раГи бољег приказа"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ŠŸŃ€Š¾Š¼ŠµŠ½ŠøŃ‚Šµ Ń€Š°Š·Š¼ŠµŃ€Ńƒ ове Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Šµ у ПоГешавањима"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ŠŸŃ€Š¾Š¼ŠµŠ½Šø Ń€Š°Š·Š¼ŠµŃ€Ńƒ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Š˜Š¼Š°Ń‚Šµ проблема са камером?\nДоГирните Га бисте поново уклопили"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼ није Ń€ŠµŃˆŠµŠ½?\nДоГирните Га бисте вратили"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŠµŠ¼Š°Ń‚Šµ проблема са камером? ДоГирните Га бисте оГбацили."</string>
@@ -89,7 +97,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ŠŸŃ€Š¾ŃˆŠøŃ€ŠøŃ‚Šµ за још ŠøŠ½Ń„Š¾Ń€Š¼Š°Ń†ŠøŃ˜Š°."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли Га Ń€ŠµŃŃ‚Š°Ń€Ń‚ŃƒŃ˜ŠµŃ‚Šµ раГи бољег приказа?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ŠœŠ¾Š¶ŠµŃ‚Šµ Га Ń€ŠµŃŃ‚Š°Ń€Ń‚ŃƒŃ˜ŠµŃ‚Šµ Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Ńƒ Га би изглеГала боље на ŠµŠŗŃ€Š°Š½Ńƒ, с тим ŃˆŃ‚Š¾ можете Га ŠøŠ·Š³ŃƒŠ±ŠøŃ‚е оно ŃˆŃ‚Š¾ сте ŃƒŃ€Š°Š“ŠøŠ»Šø или Š½ŠµŃŠ°Ń‡ŃƒŠ²Š°Š½Šµ промене, ако ŠøŃ… има"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ŠœŠ¾Š¶ŠµŃ‚Šµ Га Ń€ŠµŃŃ‚Š°Ń€Ń‚ŃƒŃ˜ŠµŃ‚Šµ Š°ŠæŠ»ŠøŠŗŠ°Ń†ŠøŃ˜Ńƒ Га би изглеГала боље на ŠµŠŗŃ€Š°Š½Ńƒ, али можете Га ŠøŠ·Š³ŃƒŠ±ŠøŃ‚е напреГак или Š½ŠµŃŠ°Ń‡ŃƒŠ²Š°Š½Šµ промене"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"ŠžŃ‚ŠŗŠ°Š¶Šø"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Š ŠµŃŃ‚Š°Ń€Ń‚ŃƒŃ˜"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ŠŠµ ŠæŃ€ŠøŠŗŠ°Š·ŃƒŃ˜ поново"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 33652cd74fda..4c22964e125f 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Flytta hƶgst upp till hƶger"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Flytta lƤngst ned till vƤnster"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytta lƤngst ned till hƶger"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"utƶka <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"komprimera <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"InstƤllningar fƶr <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"StƤng bubbla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Visa inte konversationen i bubblor"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Inga nya bubblor"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"De senaste bubblorna och ignorerade bubblor visas hƤr"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta med bubblor"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nya konversationer visas som ikoner nere i hƶrnet pƄ skƤrmen. Tryck fƶr att utƶka eller dra fƶr att stƤnga dem."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Styr bubblor nƤr som helst"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tryck hƤr fƶr att hantera vilka appar och konversationer som fƄr visas i bubblor"</string>
<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="4564728020654658478">"Tryck fƶr att starta om appen och fƄ en bƤttre vy"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Ƅndra appens bildformat i instƤllningarna"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Ƅndra bildformat"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fe2ad1f1846d..71aeb61538cd 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sogeza juu kulia"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sogeza chini kushoto"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sogeza chini kulia"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"panua <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"kunja <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mipangilio ya <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ondoa kiputo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Usiweke viputo kwenye mazungumzo"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Nimeelewa"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hakuna viputo vya hivi majuzi"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Viputo vya hivi karibuni na vile vilivyoondolewa vitaonekana hapa"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Piga gumzo ukitumia viputo"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Mazungumzo mapya huonekana kama aikoni katika kona ya chini ya skrini yako. Gusa ili uyapanue au buruta ili uyaondoe."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Dhibiti viputo wakati wowote"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Gusa hapa ili udhibiti programu na mazungumzo yanayoweza kutumia viputo"</string>
<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="4564728020654658478">"Gusa ili uzime kisha uwashe programu hii, ili upate mwonekano bora"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Badilisha uwiano wa programu hii katika Mipangilio"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Badilisha uwiano wa kipengele"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index fd5f0e646d9b..aab05daca0ae 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"மேலே ą®µą®²ą®ŖąÆą®ŖąÆą®±ą®®ą®¾ą®• ą®Øą®•ą®°ąÆą®¤ąÆą®¤ąÆ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"கீஓே ą®‡ą®Ÿą®ŖąÆą®ŖąÆą®±ą®®ą®¾ą®• ą®Øą®•ą®°ąÆą®¤ąÆą®¤ąÆ"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"கீஓே வலதுபுறமாக ą®Øą®•ą®°ąÆą®¤ąÆą®¤ąÆ"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ஐ ą®µą®æą®°ą®æą®µą®¾ą®•ąÆą®•ąÆą®®ąÆ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą®ą®šąÆ ą®šąÆą®°ąÆą®•ąÆą®•ąÆą®®ąÆ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą®…ą®®ąÆˆą®ŖąÆą®ŖąÆą®•ą®³ąÆ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ą®•ąÆą®®ą®æą®“ąÆˆ ą®…ą®•ą®±ąÆą®±ąÆ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą®‰ą®°ąÆˆą®Æą®¾ą®Ÿą®²ąÆˆą®•ąÆ ą®•ąÆą®®ą®æą®“ą®¾ą®•ąÆą®•ą®¾ą®¤ąÆ‡"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"சரி"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą®šą®®ąÆ€ą®Ŗą®¤ąÆą®¤ą®æą®Æ ą®•ąÆą®®ą®æą®“ąÆą®•ą®³ąÆ ą®‡ą®²ąÆą®²ąÆˆ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą®šą®®ąÆ€ą®Ŗą®¤ąÆą®¤ą®æą®Æ ą®•ąÆą®®ą®æą®“ąÆą®•ą®³ąÆą®®ąÆ ą®Øą®æą®°ą®¾ą®•ą®°ą®æą®•ąÆą®•ą®ŖąÆą®Ŗą®ŸąÆą®Ÿ ą®•ąÆą®®ą®æą®“ąÆą®•ą®³ąÆą®®ąÆ ą®‡ą®™ąÆą®•ąÆ‡ ą®¤ąÆ‹ą®©ąÆą®±ąÆą®®ąÆ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą®•ąÆą®®ą®æą®“ąÆą®•ą®³ąÆˆą®ŖąÆ ą®Ŗą®Æą®©ąÆą®Ŗą®ŸąÆą®¤ąÆą®¤ą®æ ą®‰ą®°ąÆˆą®Æą®¾ą®ŸąÆą®™ąÆą®•ą®³ąÆ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"புதிய ą®‰ą®°ąÆˆą®Æą®¾ą®Ÿą®²ąÆą®•ą®³ąÆ ą®‰ą®™ąÆą®•ą®³ąÆ ą®¤ą®æą®°ąÆˆą®Æą®æą®©ąÆ ą®•ąÆ€ą®“ąÆ ą®®ąÆ‚ą®²ąÆˆą®Æą®æą®²ąÆ ą®ą®•ą®¾ą®©ąÆą®•ą®³ą®¾ą®•ą®¤ąÆ ą®¤ąÆ‹ą®©ąÆą®±ąÆą®®ąÆ. ą®…ą®µą®±ąÆą®±ąÆˆ ą®µą®æą®°ą®æą®µą®¾ą®•ąÆą®• ą®¤ą®ŸąÆą®Ÿą®µąÆą®®ąÆ ą®…ą®²ąÆą®²ą®¤ąÆ ą®Øą®æą®°ą®¾ą®•ą®°ą®æą®•ąÆą®• ą®‡ą®“ąÆą®•ąÆą®•ą®µąÆą®®ąÆ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ą®Žą®ŖąÆą®ŖąÆ‹ą®¤ąÆ ą®µąÆ‡ą®£ąÆą®ŸąÆą®®ą®¾ą®©ą®¾ą®²ąÆą®®ąÆ ą®•ąÆą®®ą®æą®“ąÆą®•ą®³ąÆˆą®•ąÆ ą®•ą®ŸąÆą®ŸąÆą®ŖąÆą®Ŗą®ŸąÆą®¤ąÆą®¤ąÆą®™ąÆą®•ą®³ąÆ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą®Žą®ØąÆą®¤ąÆ†ą®ØąÆą®¤ ą®†ą®ŖąÆą®øąÆą®®ąÆ ą®‰ą®°ąÆˆą®Æą®¾ą®Ÿą®²ąÆą®•ą®³ąÆą®®ąÆ ą®•ąÆą®®ą®æą®“ą®æą®Æą®¾ą®•ą®²ą®¾ą®®ąÆ ą®Žą®©ąÆą®Ŗą®¤ąÆˆ ą®Øą®æą®°ąÆą®µą®•ą®æą®•ąÆą®• ą®‡ą®™ąÆą®•ąÆ‡ ą®¤ą®ŸąÆą®ŸąÆą®™ąÆą®•ą®³ąÆ"</string>
<string name="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="4564728020654658478">"ą®‡ą®™ąÆą®•ąÆ ą®¤ą®ŸąÆą®ŸąÆą®µą®¤ą®©ąÆ ą®®ąÆ‚ą®²ą®®ąÆ ą®‡ą®ØąÆą®¤ ą®†ą®ŖąÆą®øąÆˆ ą®®ąÆ€ą®£ąÆą®ŸąÆą®®ąÆ ą®¤ąÆŠą®Ÿą®™ąÆą®•ą®æ, ą®†ą®ŖąÆą®øąÆ ą®•ą®¾ą®ŸąÆą®Ÿą®ŖąÆą®Ŗą®ŸąÆą®®ąÆ ą®µą®æą®¤ą®¤ąÆą®¤ąÆˆ ą®‡ą®©ąÆą®©ąÆą®®ąÆ ą®šą®æą®±ą®ŖąÆą®Ŗą®¾ą®•ąÆą®•ą®²ą®¾ą®®ąÆ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą®…ą®®ąÆˆą®ŖąÆą®ŖąÆą®•ą®³ą®æą®²ąÆ ą®‡ą®ØąÆą®¤ ą®†ą®ŖąÆą®øą®æą®©ąÆ ą®¤ąÆ‹ą®±ąÆą®± ą®µą®æą®•ą®æą®¤ą®¤ąÆą®¤ąÆˆ ą®®ą®¾ą®±ąÆą®±ąÆą®®ąÆ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą®¤ąÆ‹ą®±ąÆą®± ą®µą®æą®•ą®æą®¤ą®¤ąÆą®¤ąÆˆ ą®®ą®¾ą®±ąÆą®±ąÆą®®ąÆ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா ą®¤ąÆŠą®Ÿą®°ąÆą®Ŗą®¾ą®© ą®šą®æą®•ąÆą®•ą®²ąÆą®•ą®³ą®¾?\ną®®ąÆ€ą®£ąÆą®ŸąÆą®®ąÆ ą®ŖąÆŠą®°ąÆą®¤ąÆą®¤ ą®¤ą®ŸąÆą®Ÿą®µąÆą®®ąÆ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ą®šą®æą®•ąÆą®•ą®²ąÆą®•ą®³ąÆ ą®šą®°ą®æą®šąÆ†ą®ÆąÆą®Æą®ŖąÆą®Ŗą®Ÿą®µą®æą®²ąÆą®²ąÆˆą®Æą®¾?\ną®®ą®¾ą®±ąÆą®±ą®æą®Æą®®ąÆˆą®•ąÆą®• ą®¤ą®ŸąÆą®Ÿą®µąÆą®®ąÆ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா ą®¤ąÆŠą®Ÿą®°ąÆą®Ŗą®¾ą®© ą®šą®æą®•ąÆą®•ą®²ąÆą®•ą®³ąÆ ą®Žą®¤ąÆą®µąÆą®®ąÆ ą®‡ą®²ąÆą®²ąÆˆą®Æą®¾? ą®Øą®æą®°ą®¾ą®•ą®°ą®æą®•ąÆą®• ą®¤ą®ŸąÆą®Ÿą®µąÆą®®ąÆ."</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 6f95aa9c8305..1e407bf255de 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ą°Žą°—ą±ą°µ ą°•ą±ą°”ą°æą°µą±ˆą°Ŗą±ą°Øą°•ą± జరుపు"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"దిగువ ą°Žą°”ą°®ą°µą±ˆą°Ŗą±ą°Øą°•ą± ą°¤ą°°ą°²ą°æą°‚ą°šą±"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"దిగవు ą°•ą±ą°”ą°æą°µą±ˆą°Ŗą±ą°Øą°•ą± జరుపు"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ą°µą°æą°øą±ą°¤ą°°ą°æą°‚ą°šą°‚ą°”ą°æ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€Œą°Øą± ą°•ą±ą°¦ą°æą°‚ą°šą°‚ą°”ą°æ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ą°øą±†ą°Ÿą±ą°Ÿą°æą°‚ą°—ą±ā€Œą°²ą±"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ą°¬ą°¬ą±ą°²ą±ā€Œą°Øą± ą°µą°æą°øą±ą°®ą°°ą°æą°‚ą°šą±"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"సంభాషణను ą°¬ą°¬ą±ą°²ą± ą°šą±‡ą°Æą°µą°¦ą±ą°¦ą±"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ą°…ą°°ą±ą°„ą°®ą±ˆą°‚ą°¦ą°æ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą°‡ą°Ÿą±€ą°µą°²ą°æ ą°¬ą°¬ą±ą°²ą±ą°øą± ą°ą°µą±€ లేవు"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ą°‡ą°Ÿą±€ą°µą°²ą°æ ą°¬ą°¬ą±ą°²ą±ą°øą± మరియు తీసివేసిన ą°¬ą°¬ą±ą°²ą±ą°øą± ą°‡ą°•ą±ą°•ą°” ą°•ą°Øą°æą°Ŗą°æą°øą±ą°¤ą°¾ą°Æą°æ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą°¬ą°¬ą±ą°²ą±ą°øą±ā€Œą°Øą± ą°‰ą°Ŗą°Æą±‹ą°—ą°æą°‚ą°šą°æ ą°šą°¾ą°Ÿą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ą°•ą±Šą°¤ą±ą°¤ సంభాషణలు మీ ą°øą±ą°•ą±ą°°ą±€ą°Øą± ą°•ą°æą°‚ą°¦ మూలన ą°šą°æą°¹ą±ą°Øą°¾ą°²ą±ą°—ą°¾ ą°•ą°Øą°æą°Ŗą°æą°øą±ą°¤ą°¾ą°Æą°æ. ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°øą°æ వాటిని ą°µą°æą°øą±ą°¤ą°°ą°æą°‚ą°šą°‚ą°”ą°æ లేదా లాగి ą°µą°æą°øą±ą°®ą°°ą°æą°‚ą°šą°‚ą°”ą°æ."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ą°¬ą°¬ą±ą°²ą±ą°øą±ā€Œą°Øą± ą°Žą°Ŗą±ą°Ŗą±ą°”ą±ˆą°Øą°¾ ą°•ą°‚ą°Ÿą±ą°°ą±‹ą°²ą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą° ą°Æą°¾ą°Ŗą±ā€Œą°²ą±, సంభాషణలను ą°¬ą°¬ą±ą°²ą± ą°šą±‡ą°Æą°¾ą°²ą±‹ ą°®ą±‡ą°Øą±‡ą°œą± ą°šą±‡ą°Æą°”ą°¾ą°Øą°æą°•ą°æ ą°‡ą°•ą±ą°•ą°” ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
<string name="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="4564728020654658478">"ą°®ą±†ą°°ą±ą°—ą±ˆą°Ø ą°µą±€ą°•ą±ą°·ą°£ కోసం ఈ ą°Æą°¾ą°Ŗą±ā€Œą°Øą± ą°°ą±€ą°øą±ą°Ÿą°¾ą°°ą±ą°Ÿą± ą°šą±‡ą°Æą°”ą°¾ą°Øą°æą°•ą°æ ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą°øą±†ą°Ÿą±ą°Ÿą°æą°‚ą°—ą±ā€Œą°²ą°²ą±‹ ఈ ą°Æą°¾ą°Ŗą± ఆకార ą°Øą°æą°·ą±ą°Ŗą°¤ą±ą°¤ą°æą°Øą°æ ą°®ą°¾ą°°ą±ą°šą°‚ą°”ą°æ"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ఆకార ą°Øą°æą°·ą±ą°Ŗą°¤ą±ą°¤ą°æą°Øą°æ ą°®ą°¾ą°°ą±ą°šą°‚ą°”ą°æ"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా ą°øą°®ą°øą±ą°Æą°²ą± ą°‰ą°Øą±ą°Øą°¾ą°Æą°¾?\ną°°ą±€ą°«ą°æą°Ÿą± ą°šą±‡ą°Æą°”ą°¾ą°Øą°æą°•ą°æ ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని ą°øą°®ą°øą±ą°Æą°Øą± ą°Ŗą°°ą°æą°·ą±ą°•ą°°ą°æą°‚ą°šą°²ą±‡ą°¦ą°¾?\ną°Ŗą±‚ą°°ą±ą°µą°øą±ą°„ą°æą°¤ą°æą°•ą°æ ą°®ą°¾ą°°ą±ą°šą°”ą°¾ą°Øą°æą°•ą°æ ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°Æą°‚ą°”ą°æ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా ą°øą°®ą°øą±ą°Æą°²ą± లేవా? తీసివేయఔానికి ą°Ÿą±ą°Æą°¾ą°Ŗą± ą°šą±‡ą°Æą°‚ą°”ą°æ."</string>
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index cc0333efd82b..8d2e28b9492f 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -45,11 +45,16 @@
<integer name="config_pipForceCloseDelay">5000</integer>
<!-- Animation duration when exit starting window: fade out icon -->
- <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer>
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer>
- <!-- Animation duration when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_delay">0</integer>
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">200</integer>
<!-- Animation duration when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_duration">0</integer>
+ <integer name="starting_window_app_reveal_anim_duration">300</integer>
+
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">1</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 6733940fd445..77a9f4b3ec76 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ąø¢ą¹‰ąø²ąø¢ą¹„ąø›ąø”ą¹‰ąø²ąø™ąø‚ąø§ąø²ąøšąø™"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ąø¢ą¹‰ąø²ąø¢ą¹„ąø›ąø”ą¹‰ąø²ąø™ąø‹ą¹‰ąø²ąø¢ąø„ą¹ˆąø²ąø‡"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ąø¢ą¹‰ąø²ąø¢ą¹„ąø›ąø”ą¹‰ąø²ąø™ąø‚ąø²ąø§ąø„ą¹ˆąø²ąø‡"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ขยาย <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ยุบ <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø² <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ąø›ąø“ąø”ąøšąø±ąøšą¹€ąøšąø“ąø„"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ą¹„ąø”ą¹ˆąø•ą¹‰ąø­ąø‡ą¹ąøŖąø”ąø‡ąøąø²ąø£ąøŖąø™ąø—ąø™ąø²ą¹€ąø›ą¹‡ąø™ąøšąø±ąøšą¹€ąøšąø“ąø„"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ąø£ąø±ąøšąø—ąø£ąø²ąøš"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ą¹„ąø”ą¹ˆąø”ąøµąøšąø±ąøšą¹€ąøšąø“ąø„ą¹€ąø”ąø·ą¹ˆąø­ą¹€ąø£ą¹‡ąø§ą¹† นี้"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ąøšąø±ąøšą¹€ąøšąø“ąø„ąø—ąøµą¹ˆą¹ąøŖąø”ąø‡ą¹ąø„ąø°ąø—ąøµą¹ˆąø›ąø“ąø”ą¹„ąø›ą¹€ąø”ąø·ą¹ˆąø­ą¹€ąø£ą¹‡ąø§ą¹† ąø™ąøµą¹‰ąøˆąø°ąø›ąø£ąø²ąøąøąø—ąøµą¹ˆąø™ąøµą¹ˆ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ą¹ąøŠąø—ą¹‚ąø”ąø¢ą¹ƒąøŠą¹‰ąøšąø±ąøšą¹€ąøšąø“ąø„"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ąøąø²ąø£ąøŖąø™ąø—ąø™ąø²ąø„ąø£ąø±ą¹‰ąø‡ą¹ƒąø«ąø”ą¹ˆą¹† ąøˆąø°ąø›ąø£ąø²ąøąøą¹€ąø›ą¹‡ąø™ą¹„ąø­ąø„ąø­ąø™ąø—ąøµą¹ˆąø”ąøøąø”ąø„ą¹ˆąø²ąø‡ąø‚ąø­ąø‡ąø«ąø™ą¹‰ąø²ąøˆąø­ ą¹‚ąø”ąø¢ąøŖąø²ąø”ąø²ąø£ąø–ą¹ąø•ąø°ą¹€ąøžąø·ą¹ˆąø­ąø‚ąø¢ąø²ąø¢ąø«ąø£ąø·ąø­ąø„ąø²ąøą¹€ąøžąø·ą¹ˆąø­ąø›ąø“ąø”"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ąø„ąø§ąøšąø„ąøøąø”ąøšąø±ąøšą¹€ąøšąø“ąø„ą¹„ąø”ą¹‰ąø—ąøøąøą¹€ąø”ąø·ą¹ˆąø­"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ą¹ąø•ąø°ąø—ąøµą¹ˆąø™ąøµą¹ˆą¹€ąøžąø·ą¹ˆąø­ąøˆąø±ąø”ąøąø²ąø£ą¹ąø­ąø›ą¹ąø„ąø°ąøąø²ąø£ąøŖąø™ąø—ąø™ąø²ąø—ąøµą¹ˆą¹ąøŖąø”ąø‡ą¹€ąø›ą¹‡ąø™ąøšąø±ąøšą¹€ąøšąø“ąø„ą¹„ąø”ą¹‰"</string>
<string name="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="4564728020654658478">"ą¹ąø•ąø°ą¹€ąøžąø·ą¹ˆąø­ąø£ąøµąøŖąø•ąø²ąø£ą¹Œąø—ą¹ąø­ąø›ąø™ąøµą¹‰ą¹ąø„ąø°ąø£ąø±ąøšąø”ąøøąø”ąø”ąø­ąø‡ąø—ąøµą¹ˆąø”ąøµąø¢ąø“ą¹ˆąø‡ąø‚ąø¶ą¹‰ąø™"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąøŖąø±ąø”ąøŖą¹ˆąø§ąø™ąø ąø²ąøžąø‚ąø­ąø‡ą¹ąø­ąø›ąø™ąøµą¹‰ą¹ƒąø™ąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø²"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąø­ąø±ąø•ąø£ąø²ąøŖą¹ˆąø§ąø™ąøąø§ą¹‰ąø²ąø‡ąø¢ąø²ąø§"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ąø«ąø²ąøąøžąøšąø›ąø±ąøąø«ąø²ąøąø±ąøšąøąø„ą¹‰ąø­ąø‡\ną¹ąø•ąø°ą¹€ąøžąø·ą¹ˆąø­ą¹ąøą¹‰ą¹„ąø‚"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ąø«ąø²ąøą¹„ąø”ą¹ˆą¹„ąø”ą¹‰ą¹ąøą¹‰ą¹„ąø‚\ną¹ąø•ąø°ą¹€ąøžąø·ą¹ˆąø­ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąøąø„ąø±ąøš"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ąø«ąø²ąøą¹„ąø”ą¹ˆąøžąøšąø›ąø±ąøąø«ąø²ąøąø±ąøšąøąø„ą¹‰ąø­ąø‡ ą¹ąø•ąø°ą¹€ąøžąø·ą¹ˆąø­ąø›ąø“ąø”"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 8cf4eb484378..757da92ef72c 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Ilipat sa kanan sa itaas"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Ilipat sa kaliwa sa ibaba"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ilipat sa kanan sa ibaba"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"I-expand ang <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"i-collapse ang <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mga setting ng <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"I-dismiss ang bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Huwag ipakita sa bubble ang mga pag-uusap"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Walang kamakailang bubble"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Lalabas dito ang mga kamakailang bubble at na-dismiss na bubble"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Mag-chat gamit ang mga bubble"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Lalabas ang mga bagong pag-uusap bilang mga icon sa sulok sa ibaba ng iyong screen. I-tap para i-expand ang mga ito o i-drag para i-dismiss ang mga ito."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kontrolin ang mga bubble anumang oras"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Mag-tap dito para pamahalaan ang mga app at conversion na puwedeng mag-bubble"</string>
<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="4564728020654658478">"I-tap para i-restart ang app na ito para sa mas magandang view"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Baguhin ang aspect ratio ng app na ito sa Mga Setting"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Baguhin ang aspect ratio"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 1454435b3de7..9c4bf8df9308 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Sağ üste taşı"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Sol alta taşı"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sağ alta taşı"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"genişlet: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"daralt: <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baloncuğu kapat"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Görüşmeyi baloncuk olarak görüntüleme"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Anladım"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Son kapatılan baloncuk yok"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Son baloncuklar ve kapattığınız baloncuklar burada görünür"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Baloncukları kullanarak sohbet edin"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yeni görüşmeler, ekranınızın alt köşesinde simge olarak görünür. Bunları dokunarak genişletebilir veya sürükleyerek kapatabilirsiniz."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Baloncukları istediğiniz zaman kontrol edin"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Buraya dokunarak baloncuk olarak gösterilecek uygulama ve görüşmeleri yönetin"</string>
<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="4564728020654658478">"Bu uygulamayı yeniden başlatarak daha iyi bir görünüm elde etmek için dokunun"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Bu uygulamanın en boy oranını Ayarlar\'dan değiştirin"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"En boy oranını değiştir"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 78df129d8f50..753fe29ebd41 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ŠŸŠµŃ€ŠµŠ¼Ń–ŃŃ‚ŠøŃ‚Šø ŠæŃ€Š°Š²Š¾Ń€ŃƒŃ‡ ŃƒŠ³Š¾Ń€Ńƒ"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ŠŸŠµŃ€ŠµŠ¼Ń–ŃŃ‚ŠøŃ‚Šø Š»Ń–Š²Š¾Ń€ŃƒŃ‡ униз"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ŠŸŠµŃ€ŠµŠ¼Ń–ŃŃ‚ŠøŃ‚Šø ŠæŃ€Š°Š²Š¾Ń€ŃƒŃ‡ униз"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"Ń€Š¾Š·Š³Š¾Ń€Š½ŃƒŃ‚Šø \"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>\""</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"Š·Š³Š¾Ń€Š½ŃƒŃ‚Šø \"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>\""</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ŠŠ°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń параметра \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Закрити ŠæŃ–Š“ŠŗŠ°Š·ŠŗŃƒ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ŠŠµ ŠæŠ¾ŠŗŠ°Š·ŃƒŠ²Š°Ń‚Šø ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡Ń– чати Š“Š»Ń розмов"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Š—Ń€Š¾Š·ŃƒŠ¼Ń–Š»Š¾"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ŠŠµŠ¼Š°Ń” нещоГавніх ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡ŠøŃ… чатів"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Š¢ŃƒŃ‚ Š·\'ŃŠ²Š»ŃŃ‚ŠøŠ¼ŃƒŃ‚ŃŒŃŃ нещоГавні й закриті ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡Ń– чати"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Š”ŠæŠ»ŠøŠ²Š°ŃŽŃ‡ŠøŠ¹ чат"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ŠŠ¾Š²Ń– розмови Š²Ń–Š“Š¾Š±Ń€Š°Š¶Š°ŃŽŃ‚ŃŒŃŃ у Š²ŠøŠ³Š»ŃŠ“Ń– значків у нижньому ŠŗŃƒŃ‚Ń– екрана. Š¢Š¾Ń€ŠŗŠ½Ń–Ń‚ŃŒŃŃ, щоб Ń€Š¾Š·Š³Š¾Ń€Š½ŃƒŃ‚Šø їх, або ŠæŠµŃ€ŠµŃ‚ŃŠ³Š½Ń–Ń‚ŃŒ, щоб закрити."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ŠšŠ¾Š½Ń‚Ń€Š¾Š»ŃŒ ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡ŠøŃ… чатів"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ŠŠ°Ń‚ŠøŃŠ½Ń–Ń‚ŃŒ тут, щоб вибрати, Š“Š»Ń ŃŠŗŠøŃ… ГоГатків і розмов Гозволити ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡Ń– чати"</string>
<string name="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="4564728020654658478">"ŠŠ°Ń‚ŠøŃŠ½Ń–Ń‚ŃŒ, щоб ŠæŠµŃ€ŠµŠ·Š°ŠæŃƒŃŃ‚ŠøŃ‚Šø цей ГоГаток Š“Š»Ń Š·Ń€ŃƒŃ‡Š½Ń–ŃˆŠ¾Š³Š¾ ŠæŠµŃ€ŠµŠ³Š»ŃŠ“Ńƒ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Змінити формат Š“Š»Ń Ń†ŃŒŠ¾Š³Š¾ ГоГатка в Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½ŃŃ…"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Змінити формат"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼Šø Š· ŠŗŠ°Š¼ŠµŃ€Š¾ŃŽ?\nŠŠ°Ń‚ŠøŃŠ½Ń–Ń‚ŃŒ, щоб ŠæŃ€ŠøŃŃ‚Š¾ŃŃƒŠ²Š°Ń‚Šø"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ŠŸŃ€Š¾Š±Š»ŠµŠ¼Ńƒ не Š²ŠøŃ€Ń–ŃˆŠµŠ½Š¾?\nŠŠ°Ń‚ŠøŃŠ½Ń–Ń‚ŃŒ, щоб ŃŠŗŠ°ŃŃƒŠ²Š°Ń‚Šø зміни"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ŠŠµŠ¼Š°Ń” проблем Ń–Š· ŠŗŠ°Š¼ŠµŃ€Š¾ŃŽ? Š¢Š¾Ń€ŠŗŠ½Ń–Ń‚ŃŒŃŃ, щоб закрити."</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index ca1642488768..fb0137658560 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"اوپر دائیں جانب لے جائيں"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Ł†ŪŒŚ†Ū’ بائیں جانب لے جائیں"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ł†ŪŒŚ†Ū’ دائیں جانب لے جائیں"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> کو Ł¾Ś¾ŪŒŁ„Ų§Ų¦ŪŒŚŗ"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> کو Ų³Ś©ŪŒŚ‘ŪŒŚŗ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ترتیبات"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"بلبلہ ŲØŲ±Ų®Ų§Ų³ŲŖ کریں"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"گفتگو بلبلہ نہ کریں"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"سمجھ Ų¢ گئی"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"کوئی Ų­Ų§Ł„ŪŒŪ بلبلہ Ł†ŪŪŒŚŗ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Ų­Ų§Ł„ŪŒŪ بلبلے اور ŲØŲ±Ų®Ų§Ų³ŲŖ ؓدہ بلبلے یہاں ظاہر ہوں ŚÆŪ’"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"بلبلے کے Ų°Ų±ŪŒŲ¹Ū’ Ś†ŪŒŁ¹ کریں"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Ł†Ų¦ŪŒ گفتگوئیں آپ کی Ų§Ų³Ś©Ų±ŪŒŁ† کے Ł†ŪŒŚ†Ū’ Ś©ŁˆŁ†Ū’ Ł…ŪŒŚŗ Ų¢Ų¦ŪŒŚ©Ł†Ų² کے طور پر ظاہر ہوتی ŪŪŒŚŗŪ” Ų§Ł†ŪŪŒŚŗ Ł¾Ś¾ŪŒŁ„Ų§Ł†Ū’ کے Ł„ŪŒŪ’ تھپتھپائیں یا Ų§Ł†ŪŪŒŚŗ ŲØŲ±Ų®Ų§Ų³ŲŖ کرنے کے Ł„ŪŒŪ’ ŚÆŚ¾Ų³ŪŒŁ¹ŪŒŚŗŪ”"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کسی بھی ŁˆŁ‚ŲŖ بلبلے کو Ś©Ł†Ł¹Ų±ŁˆŁ„ کریں"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"یہ نظم کرنے کے Ł„ŪŒŪ’ یہاں تھپتھپائیں کہ Ś©ŁˆŁ† سی ایپس اور گفتگوئیں بلبلہ سکتی ہیں"</string>
<string name="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="4564728020654658478">"بہتر منظر کے Ł„ŪŒŪ’ Ų§Ų³ ایپ کو ری اسٹارٹ کرنے کی Ų®Ų§Ų·Ų± تھپتھپائیں"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"ترتیبات Ł…ŪŒŚŗ Ų§Ų³ ایپ کی ŲŖŁ†Ų§Ų³ŲØŪŒ Ų“Ų±Ų­ کو ŲŖŲØŲÆŪŒŁ„ کریں"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ŲŖŁ†Ų§Ų³ŲØŪŒ Ų“Ų±Ų­ کو ŲŖŲØŲÆŪŒŁ„ کریں"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ś©ŪŒŁ…Ų±Ū’ کے Ł…Ų³Ų§Ų¦Ł„ŲŸ\nدوبارہ فٹ کرنے Ś©ŪŒŁ„Ų¦Ū’ تھپتھپائیں"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ Ų­Ł„ Ł†ŪŪŒŚŗ ہوا؟\nŁ„ŁˆŁ¹Ų§Ł†Ū’ Ś©ŪŒŁ„Ų¦Ū’ تھپتھپائیں"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی Ś©ŪŒŁ…Ų±Ū’ کا مسئلہ Ł†ŪŪŒŚŗ ŪŪ’ŲŸ ŲØŲ±Ų®Ų§Ų³ŲŖ کرنے Ś©ŪŒŁ„Ų¦Ū’ ŲŖŚ¾Ł¾ŲŖŚ¾Ł¾Ų§Ų¦ŪŒŚŗŪ”"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index c0dc03366cd6..81e63de9767c 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Yuqori oʻngga surish"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Quyi chapga surish"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Quyi oʻngga surish"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ni yoyish"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ni yopish"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> sozlamalari"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bulutchani yopish"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Suhbatlar bulutchalar shaklida chiqmasin"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Hech qanday bulutcha topilmadi"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Eng oxirgi va yopilgan bulutchali chatlar shu yerda chiqadi"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Bulutchalar yordamida suhbatlashish"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Yangi suhbatlar ekraningizning pastki burchagida belgilar shaklida koʻrinadi. Ochish uchun bosing yoki yopish uchun torting"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bulutchalardagi bildirishnomalar"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Bulutchalarda bildirishnomalar chiqishiga ruxsat beruvchi ilova va suhbatlarni tanlang."</string>
<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="4564728020654658478">"Yaxshiroq koʻrish maqsadida bu ilovani qayta ishga tushirish uchun bosing"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Sozlamalar orqali bu ilovaning tomonlar nisbatini oʻzgartiring"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Tomonlar nisbatini oʻzgartirish"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 7d974005dd31..16bbd5eda23e 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Chuyển lên trên cùng bên phải"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Chuyển tį»›i dưới cùng bĆŖn trĆ”i"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Chuyển tį»›i dưới cùng bĆŖn phįŗ£i"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"mở rį»™ng <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"thu gį»n <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"CĆ i đặt <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Đóng bong bóng"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Dừng sį»­ dỄng bong bóng cho cuį»™c trò chuyện"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Đã hiểu"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"KhĆ“ng có bong bóng trò chuyện nĆ o gįŗ§n đây"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Bong bóng trò chuyện đã đóng vĆ  bong bóng trò chuyện gįŗ§n đây sįŗ½ xuįŗ„t hiện ở đây"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Trò chuyện bįŗ±ng bong bóng trò chuyện"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"CĆ”c cuį»™c trò chuyện mį»›i sįŗ½ xuįŗ„t hiện dưới dįŗ”ng biểu tượng ở góc dưới mĆ n hƬnh. HĆ£y nhįŗ„n vĆ o cĆ”c cuį»™c trò chuyện đó Ä‘į»ƒ mở rį»™ng hoįŗ·c kĆ©o Ä‘į»ƒ bį» qua."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Kiểm soÔt bong bóng bẄt cứ lúc nào"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Nhįŗ„n vĆ o đây Ä‘į»ƒ quįŗ£n lý việc dùng bong bóng cho cĆ”c ứng dỄng vĆ  cuį»™c trò chuyện"</string>
<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="4564728020654658478">"Nhįŗ„n nĆŗt khởi động lįŗ”i ứng dỄng nĆ y Ä‘į»ƒ xem dį»… hĘ”n"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Thay đổi tį»· lệ khung hƬnh cį»§a ứng dỄng nĆ y thĆ“ng qua phįŗ§n CĆ i đặt"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Thay đổi tį»· lệ khung hƬnh"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-watch/colors.xml b/libs/WindowManager/Shell/res/values-watch/colors.xml
new file mode 100644
index 000000000000..82492bf2af80
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <color name="splash_window_background_default">@color/splash_screen_bg_dark</color>
+</resources>
+
diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml
new file mode 100644
index 000000000000..03736edc4ec6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/config.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for watch products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer>
+
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">50</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_duration">200</integer>
+
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">1</integer>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-watch/dimen.xml b/libs/WindowManager/Shell/res/values-watch/dimen.xml
new file mode 100644
index 000000000000..362e72cb4d2d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/dimen.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (48 X 48) / (72 x 72) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (60 / 48) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.25</item>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index d1f50dba1ce1..c12ec8423858 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ē§»č‡³å³äøŠč§’"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移至左下角"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ē§»č‡³å³äø‹č§’"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"å±•å¼€ā€œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ę”¶čµ·ā€œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>ā€"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"å…³é—­åÆ¹čÆę³”"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"äøä»„åÆ¹čÆę³”å½¢å¼ę˜¾ē¤ŗåÆ¹čÆ"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ēŸ„é“äŗ†"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ęœ€čæ‘ę²”ęœ‰åÆ¹čÆę³”"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ę­¤å¤„ä¼šę˜¾ē¤ŗęœ€čæ‘ēš„åÆ¹čÆę³”å’Œå·²å…³é—­ēš„åÆ¹čÆę³”"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ä½æē”ØåÆ¹čÆę³”čŠå¤©"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ę–°åÆ¹čÆä¼šä»„å›¾ę ‡å½¢å¼ę˜¾ē¤ŗåœØå±å¹•åŗ•éƒØēš„č§’č½äø­ć€‚ē‚¹ęŒ‰å›¾ę ‡å³åÆå±•å¼€åÆ¹čÆļ¼Œę‹–åŠØå›¾ę ‡å³åÆå…³é—­åÆ¹čÆć€‚"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"éšę—¶ęŽ§åˆ¶åÆ¹čÆę³”"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"ē‚¹ęŒ‰ę­¤å¤„å³åÆē®”ē†å“Ŗäŗ›åŗ”ē”Øå’ŒåÆ¹čÆåÆä»„ę˜¾ē¤ŗåÆ¹čÆę³”"</string>
<string name="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="4564728020654658478">"ē‚¹ęŒ‰å³åÆé‡åÆę­¤åŗ”ē”Øļ¼ŒčŽ·å¾—ę›“å„½ēš„č§†č§‰ä½“éŖŒ"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"åœØā€œč®¾ē½®ā€äø­ę›“ę”¹ę­¤åŗ”ē”Øēš„å®½é«˜ęÆ”"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"ę›“ę”¹é«˜å®½ęÆ”"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ē›øęœŗęœ‰é—®é¢˜ļ¼Ÿ\nē‚¹ęŒ‰å³åÆę•“äæ®"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ę²”ęœ‰č§£å†³ę­¤é—®é¢˜ļ¼Ÿ\nē‚¹ęŒ‰å³åÆę¢å¤"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ē›øęœŗę²”ęœ‰é—®é¢˜ļ¼Ÿē‚¹ęŒ‰å³åÆåæ½ē•„ć€‚"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 6f399e51be4d..c9543488688b 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -24,7 +24,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"怌<xliff:g id="NAME">%s</xliff:g>ć€ē›®å‰åœØē•«äø­ē•«ęØ”å¼"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"å¦‚ęžœę‚Øäøęƒ³ć€Œ<xliff:g id="NAME">%s</xliff:g>ć€ä½æē”Øę­¤åŠŸčƒ½ļ¼Œč«‹č¼•ęŒ‰ä»„é–‹å•ŸčØ­å®šļ¼Œē„¶å¾Œåœē”Øę­¤åŠŸčƒ½ć€‚"</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"å¦‚ęžœä½ äøęƒ³ć€Œ<xliff:g id="NAME">%s</xliff:g>ć€ä½æē”Øę­¤åŠŸčƒ½ļ¼Œč«‹č¼•ęŒ‰ä»„é–‹å•ŸčØ­å®šļ¼Œē„¶å¾Œåœē”Øę­¤åŠŸčƒ½ć€‚"</string>
<string name="pip_play" msgid="3496151081459417097">"播放"</string>
<string name="pip_pause" msgid="690688849510295232">"暫停"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"č·³åˆ°äø‹äø€å€‹"</string>
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ē§»åŽ»å³äøŠč§’"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ē§»åŽ»å·¦äø‹č§’"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ē§»åŽ»å³äø‹č§’"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"ꉓ開<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ę”¶åŸ‹<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"怌<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>ć€čØ­å®š"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"é—œé–‰å°č¦–ēŖ—ę°£ę³”"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"äøč¦é€éŽå°č¦–ēŖ—é”Æē¤ŗå°č©±"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ēŸ„é“äŗ†"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ę²’ęœ‰ęœ€čæ‘ę›¾ä½æē”Øēš„å°č¦–ēŖ—"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ęœ€čæ‘ä½æē”Øå’Œé—œé–‰ēš„å°č¦–ēŖ—ęœƒåœØé€™č£”é”Æē¤ŗ"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"ä½æē”Øå°č©±ę°£ę³”é€²č”Œå³ę™‚é€ščØŠ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ē•«é¢åŗ•éƒØēš„č§’č½ęœƒé”Æē¤ŗę–°å°č©±åœ–ē¤ŗć€‚č¼•ęŒ‰å³åÆå±•é–‹åœ–ē¤ŗļ¼›ę‹–ę›³å³åÆé—œé–‰ć€‚"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"éšØę™‚ęŽ§åˆ¶å°č©±ę°£ę³”"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"č¼•ęŒ‰é€™č£”å³åÆē®”ē†å“Ŗäŗ›ę‡‰ē”ØēØ‹å¼å’Œå°č©±åÆä»„ä½æē”Øå°č©±ę°£ę³”"</string>
<string name="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="4564728020654658478">"č¼•ęŒ‰äø¦é‡ę–°å•Ÿå‹•ę­¤ę‡‰ē”ØēØ‹å¼ļ¼Œä»„å–å¾—ę›“ä½³ēš„č§€ēœ‹é«”é©—"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"å‰å¾€ć€ŒčØ­å®šć€č®Šę›“ę­¤ę‡‰ē”ØēØ‹å¼ēš„é•·åÆ¬ęÆ”"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"č®Šę›“é•·åÆ¬ęÆ”"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ē›øę©Ÿęœ‰å•é”Œļ¼Ÿ\nč¼•ęŒ‰å³åÆäæ®ę­£"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ęœŖčƒ½äæ®ę­£å•é”Œļ¼Ÿ\nč¼•ęŒ‰å³åÆé‚„åŽŸ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ē›øę©Ÿå†‡å•é”Œļ¼Ÿć©’äø€äø‹å°±åÆä»„å³åÆé–‚å’—ä½¢ć€‚"</string>
@@ -88,8 +96,8 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"åœØę‡‰ē”ØēØ‹å¼å¤–č¼•ęŒ‰å…©äø‹å³åÆčŖæę•“ä½ē½®"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ēŸ„é“äŗ†"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"å±•é–‹å³åÆęŸ„ēœ‹č©³ęƒ…ć€‚"</string>
- <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"č¦é‡ę–°å•Ÿå‹•ę”¹å–„ęŖ¢č¦–ē•«é¢å—Žļ¼Ÿ"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ę‚ØåÆé‡ę–°å•Ÿå‹•ę‡‰ē”ØēØ‹å¼ļ¼Œč®“ē³»ēµ±ę›“ę–°ęŖ¢č¦–ē•«é¢ļ¼›ä½†ē³»ēµ±åÆčƒ½äøęœƒå„²å­˜ē›®å‰é€²åŗ¦åŠę‚Øä½œå‡ŗēš„ä»»ä½•č®Šę›“"</string>
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"č¦é‡ę–°å•Ÿå‹•ä»„ę”¹å–„ęŖ¢č¦–ē•«é¢å—Žļ¼Ÿ"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ä½ åÆé‡ę–°å•Ÿå‹•ę‡‰ē”ØēØ‹å¼ļ¼Œč®“ē³»ēµ±ę›“ę–°ęŖ¢č¦–ē•«é¢ļ¼›ä½†ē³»ēµ±åÆčƒ½äøęœƒå„²å­˜ē›®å‰é€²åŗ¦åŠä½ ä½œå‡ŗēš„ä»»ä½•č®Šę›“"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"å–ę¶ˆ"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"é‡ę–°å•Ÿå‹•"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"äøč¦å†é”Æē¤ŗ"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 4ca49e167118..d25bfd74ca04 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"ē§»č‡³å³äøŠę–¹"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"移至左下方"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ē§»č‡³å³äø‹ę–¹"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"å±•é–‹ć€Œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>怍"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"ę”¶åˆć€Œ<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>怍"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"怌<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>ć€čØ­å®š"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"é—œé–‰å°č©±ę”†"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"äøč¦ä»„å°č©±ę”†å½¢å¼é”Æē¤ŗå°č©±"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ęˆ‘ēŸ„é“äŗ†"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"ęœ€čæ‘ę²’ęœ‰ä»»ä½•å°č©±ę”†"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"ęœ€čæ‘ēš„å°č©±ę”†å’Œå·²é—œé–‰ēš„å°č©±ę”†ęœƒé”Æē¤ŗåœØé€™č£”"</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"é€éŽå°č©±ę”†é€²č”Œå³ę™‚é€ščØŠ"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"ē•«é¢åŗ•éƒØēš„č§’č½ęœƒé”Æē¤ŗę–°å°č©±åœ–ē¤ŗć€‚č¼•č§øåÆå±•é–‹åœ–ē¤ŗļ¼Œę‹–ę›³å³åÆé—œé–‰ć€‚"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"ä½ éšØę™‚åÆä»„ęŽ§ē®”å°č©±ę”†ēš„å„é …čØ­å®š"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"č¼•č§øé€™č£”å³åÆē®”ē†å“Ŗäŗ›ę‡‰ē”ØēØ‹å¼å’Œå°č©±åÆé”Æē¤ŗå°č©±ę”†"</string>
<string name="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="4564728020654658478">"č¼•č§øę­¤ęŒ‰éˆ•é‡ę–°å•Ÿå‹•é€™å€‹ę‡‰ē”ØēØ‹å¼ļ¼Œå³åÆē²å¾—ę›“č‰Æå„½ēš„č§€ēœ‹é«”é©—"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"å‰å¾€ć€ŒčØ­å®šć€č®Šę›“é€™å€‹ę‡‰ē”ØēØ‹å¼ēš„é”Æē¤ŗęÆ”ä¾‹"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"č®Šę›“é”Æē¤ŗęÆ”ä¾‹"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ē›øę©Ÿęœ‰å•é”Œå—Žļ¼Ÿ\nč¼•č§øå³åÆäæ®ę­£"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ęœŖäæ®ę­£å•é”Œå—Žļ¼Ÿ\nč¼•č§øå³åÆé‚„åŽŸ"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ē›øę©Ÿę²’å•é”Œå—Žļ¼Ÿč¼•č§øå³åÆé—œé–‰ć€‚"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 478b5a62c8a5..bd62b65ccdf2 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -66,6 +66,8 @@
<string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Hambisa phezulu ngakwesokudla"</string>
<string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Hambisa inkinobho ngakwesokunxele"</string>
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Hambisa inkinobho ngakwesokudla"</string>
+ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"nweba <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
+ <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"goqa <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> izilungiselelo"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cashisa ibhamuza"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ungayibhamuzi ingxoxo"</string>
@@ -76,10 +78,16 @@
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ngiyezwa"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Awekho amabhamuza akamuva"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Amabhamuza akamuva namabhamuza asusiwe azobonakala lapha."</string>
+ <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Xoxa usebenzisa amabhamuza"</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Izingxoxo ezintsha zivela njengezithonjana ekhoneni eliphansi lesikrini sakho. Thepha ukuze uzikhulise noma uhudule ukuze uzichithe."</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Lawula amabhamuza noma nini"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Thepha lapha ukuze ulawule ukuthi yimaphi ama-app kanye nezingxoxo ezingenza amabhamuza"</string>
<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="4564728020654658478">"Thepha ukuze uqale kabusha le app ukuze ibonakale kangcono"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Shintsha ukubukeka kwesilinganiselo kwe-app kuMasethingi"</string>
+ <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Shintsha ukubukeka kwesilinganiselo"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index b2ec98bc1b15..9bfd1b44dcca 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -68,9 +68,10 @@
<color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color>
<color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
<color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
- <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
- <color name="desktop_mode_caption_menu_buttons_color_inactive">#191C1D</color>
- <color name="desktop_mode_caption_menu_buttons_color_active">#00677E</color>
<color name="desktop_mode_resize_veil_light">#EFF1F2</color>
<color name="desktop_mode_resize_veil_dark">#1C1C17</color>
+ <color name="desktop_mode_maximize_menu_button">#DDDACD</color>
+ <color name="desktop_mode_maximize_menu_button_outline">#797869</color>
+ <color name="desktop_mode_maximize_menu_button_outline_on_hover">#606219</color>
+ <color name="desktop_mode_maximize_menu_button_on_hover">#E7E790</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a3916b71592b..74364001b6c5 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -58,6 +58,9 @@
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">1000</integer>
+ <!-- Allow PIP to resize via pinch gesture. -->
+ <bool name="config_pipEnablePinchResize">true</bool>
+
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">250</integer>
@@ -83,6 +86,11 @@
<!-- Animation duration when exit starting window: reveal app -->
<integer name="starting_window_app_reveal_anim_duration">266</integer>
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">0</integer>
+
<!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
These values are in DPs and will be converted to pixel sizes internally. -->
<string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 99526de56e4e..c4be384f48b3 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -143,7 +143,9 @@
<dimen name="bubble_expanded_view_padding">16dp</dimen>
<!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
when displaying in landscape on a large screen. -->
- <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
+ <dimen name="bubble_expanded_view_largescreen_landscape_padding">102dp</dimen>
+ <!-- The width of the expanded view on large screens. -->
+ <dimen name="bubble_expanded_view_largescreen_width">540dp</dimen>
<!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
a slight touch slop around the expanded view. -->
<dimen name="bubble_expanded_view_slop">8dp</dimen>
@@ -401,6 +403,30 @@
<!-- Height of button (32dp) + 2 * margin (5dp each). -->
<dimen name="freeform_decor_caption_height">42dp</dimen>
+ <!-- Height of desktop mode caption for freeform tasks. -->
+ <dimen name="desktop_mode_freeform_decor_caption_height">42dp</dimen>
+
+ <!-- Height of desktop mode caption for fullscreen tasks. -->
+ <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+
+ <!-- The width of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
+
+ <!-- The height of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_maximize_menu_height">112dp</dimen>
+
+ <!-- The larger of the two corner radii of the maximize menu buttons. -->
+ <dimen name="desktop_mode_maximize_menu_buttons_large_corner_radius">4dp</dimen>
+
+ <!-- The smaller of the two corner radii of the maximize menu buttons. -->
+ <dimen name="desktop_mode_maximize_menu_buttons_small_corner_radius">2dp</dimen>
+
+ <!-- The corner radius of the maximize menu. -->
+ <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen>
+
+ <!-- The radius of the Maximize menu shadow. -->
+ <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
+
<!-- The width of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
@@ -411,7 +437,10 @@
<dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen>
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen>
+ <dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
+
+ <!-- The height of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -425,6 +454,9 @@
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
+ <!-- The radius of the caption menu icon. -->
+ <dimen name="desktop_mode_caption_icon_radius">28dp</dimen>
+
<!-- The radius of the caption menu shadow. -->
<dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
@@ -439,4 +471,9 @@
<!-- The height of the area at the top of the screen where a freeform task will transition to
fullscreen if dragged until the top bound of the task is within the area. -->
<dimen name="desktop_mode_transition_area_height">16dp</dimen>
+
+ <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
+ <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+ <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
+ <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.2</item>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml
index 8831b610f44a..bc59a235517d 100644
--- a/libs/WindowManager/Shell/res/values/ids.xml
+++ b/libs/WindowManager/Shell/res/values/ids.xml
@@ -42,4 +42,6 @@
<item type="id" name="action_move_top_right"/>
<item type="id" name="action_move_bottom_left"/>
<item type="id" name="action_move_bottom_right"/>
+
+ <item type="id" name="dismiss_view"/>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 00c63d70d3a0..b556150e2ab9 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -142,6 +142,10 @@
<string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
<!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+ <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]-->
+ <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
+ <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]-->
+ <string name="bubble_accessibility_announce_collapse">collapse <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string>
<!-- Label for the button that takes the user to the notification settings for the given app. -->
<string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
@@ -163,6 +167,10 @@
<!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
<string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+ <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+ <string name="bubble_bar_education_stack_title">Chat using bubbles</string>
+ <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+ <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string>
<!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
<string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
<!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index d902fd49ba60..468cfd5260cc 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- Theme used for the activity that shows when the system forced an app to be resizable -->
<style name="ForcedResizableTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
<item name="android:windowBackground">@drawable/forced_resizable_background</item>
@@ -37,7 +38,7 @@
<item name="android:padding">16dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textFontWeight">500</item>
- <item name="android:textColor">@color/desktop_mode_caption_menu_text_color</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:background">?android:selectableItemBackground</item>
</style>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 34bf9e0dd98f..2e5448a9e8d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@ package com.android.wm.shell;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
@@ -29,6 +30,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.List;
@@ -44,9 +46,14 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
/** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
- public RootDisplayAreaOrganizer(Executor executor) {
+ public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) {
super(executor);
- List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @SuppressLint("MissingPermission") // Only called by SysUI.
+ private void onInit() {
+ final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
for (int i = infos.size() - 1; i >= 0; --i) {
onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 410ae78dba1b..ab61a48a715c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -18,6 +18,7 @@ package com.android.wm.shell;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+import android.annotation.SuppressLint;
import android.annotation.UiContext;
import android.app.ResourcesManager;
import android.content.Context;
@@ -32,11 +33,13 @@ import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
+import android.window.SystemPerformanceHinter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -58,12 +61,27 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
/** {@link DisplayAreaContext} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
+ private final SystemPerformanceHinter.DisplayRootProvider mPerfRootProvider =
+ new SystemPerformanceHinter.DisplayRootProvider() {
+ @Override
+ public SurfaceControl getRootForDisplay(int displayId) {
+ return mLeashes.get(displayId);
+ }
+ };
+
private final Context mContext;
- public RootTaskDisplayAreaOrganizer(Executor executor, Context context) {
+ public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context,
+ @NonNull ShellInit shellInit) {
super(executor);
mContext = context;
- List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @SuppressLint("MissingPermission") // Only called by SysUI.
+ private void onInit() {
+ final List<DisplayAreaAppearedInfo> infos =
+ registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
for (int i = infos.size() - 1; i >= 0; --i) {
onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
}
@@ -229,6 +247,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreaContexts.get(displayId);
}
+ @NonNull
+ public SystemPerformanceHinter.DisplayRootProvider getPerformanceRootProvider() {
+ return mPerfRootProvider;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
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 4640106b5f1c..cc9c2beff873 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
@@ -17,6 +17,8 @@
package com.android.wm.shell.activityembedding;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -199,8 +201,12 @@ class ActivityEmbeddingAnimationSpec {
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final Animation animation;
- if (shouldShowBackdrop(info, change)) {
+ if (options != null && options.getType() == ANIM_CUSTOM) {
+ animation = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
+ isEnter ? options.getEnterResId() : options.getExitResId());
+ } else 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);
@@ -223,8 +229,12 @@ class ActivityEmbeddingAnimationSpec {
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final Animation animation;
- if (shouldShowBackdrop(info, change)) {
+ if (options != null && options.getType() == ANIM_CUSTOM) {
+ animation = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
+ isEnter ? options.getEnterResId() : options.getExitResId());
+ } else 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);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce37148eaf..b4e852cfaa48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -87,42 +88,54 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
mTransitions.addHandler(this);
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean containsEmbeddingSplit = false;
- boolean containsNonEmbeddedChange = false;
- final List<TransitionInfo.Change> changes = info.getChanges();
- for (int i = changes.size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = changes.get(i);
- if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- containsNonEmbeddedChange = true;
- } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+ /** Whether ActivityEmbeddingController should animate this transition. */
+ public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ boolean containsEmbeddingChange = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
- containsEmbeddingSplit = true;
+ containsEmbeddingChange = true;
}
}
- if (!containsEmbeddingSplit) {
+ if (!containsEmbeddingChange) {
// Let the system to play the default animation if there is no ActivityEmbedding split
// window. This allows to play the app customized animation when there is no embedding,
// such as the device is in a folded state.
return false;
}
- if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+ if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
return false;
}
+
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
- if (options != null
- // Scene-transition will be handled by app side.
- && (options.getType() == ANIM_SCENE_TRANSITION
- // Use default transition handler to animate override animation.
- || isSupportedOverrideAnimation(options))) {
- return false;
+ if (options != null) {
+ // Scene-transition should be handled by app side.
+ if (options.getType() == ANIM_SCENE_TRANSITION) {
+ return false;
+ }
+ // The case of ActivityOptions#makeCustomAnimation, Activity#overridePendingTransition,
+ // and Activity#overrideActivityTransition are supported.
+ if (options.getType() == ANIM_CUSTOM) {
+ return true;
+ }
+ // Use default transition handler to animate other override animation.
+ return !isSupportedOverrideAnimation(options);
}
+ return true;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (!shouldAnimate(info)) return false;
+
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +149,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
mAnimationRunner.cancelAnimationFromMerge();
}
+ /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+ private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 9bf3b80d262e..42dc19ce838a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -52,12 +52,13 @@ public class BackAnimationBackground {
/**
* Ensures the back animation background color layer is present.
+ *
* @param startRect The start bounds of the closing target.
* @param color The background color.
* @param transaction The animation transaction.
*/
- void ensureBackground(Rect startRect, int color,
- @NonNull SurfaceControl.Transaction transaction) {
+ public void ensureBackground(
+ Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface != null) {
return;
}
@@ -81,7 +82,12 @@ public class BackAnimationBackground {
mIsRequestingStatusBarAppearance = false;
}
- void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ /**
+ * Remove the back animation background.
+ *
+ * @param transaction The animation transaction.
+ */
+ public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface == null) {
return;
}
@@ -93,11 +99,21 @@ public class BackAnimationBackground {
mIsRequestingStatusBarAppearance = false;
}
+ /**
+ * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress.
+ *
+ * @param customizer The {@link StatusBarCustomizer} to be used.
+ */
void setStatusBarCustomizer(StatusBarCustomizer customizer) {
mCustomizer = customizer;
}
- void onBackProgressed(float progress) {
+ /**
+ * Update back animation background with for the progress.
+ *
+ * @param progress Progress value from {@link android.window.BackProgressAnimator}
+ */
+ public void onBackProgressed(float progress) {
if (mCustomizer == null || mStartBounds.isEmpty()) {
return;
}
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 bb543f24a8ea..03c546dd2cf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
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;
@@ -25,6 +26,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ContentResolver;
@@ -43,7 +45,6 @@ import android.provider.Settings.Global;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
-import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -82,11 +83,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
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_ON) == SETTING_VALUE_ON;
-
public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
@@ -109,11 +105,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
/** Tracks if an uninterruptible animation is in progress */
private boolean mPostCommitAnimationInProgress = false;
+
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
- /** @see #setTriggerBack(boolean) */
- private boolean mTriggerBack;
- private FlingAnimationUtils mFlingAnimationUtils;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+
+ /** Registry for the back animations */
+ private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
@Nullable
private BackNavigationInfo mBackNavigationInfo;
@@ -123,6 +122,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
+
+ /**
+ * Tracks the current user back gesture.
+ */
+ private TouchTracker mCurrentTracker = new TouchTracker();
+
+ /**
+ * Tracks the next back gesture in case a new user gesture has started while the back animation
+ * (and navigation) associated with {@link #mCurrentTracker} have not yet finished.
+ */
+ private TouchTracker mQueuedTracker = new TouchTracker();
+
private final Runnable mAnimationTimeoutRunnable = () -> {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
MAX_ANIMATION_DURATION);
@@ -133,15 +144,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
BackAnimationAdapter mBackAnimationAdapter;
- private final TouchTracker mTouchTracker = new TouchTracker();
-
- private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
@Nullable
private IOnBackInvokedCallback mActiveCallback;
- private CrossActivityAnimation mDefaultActivityAnimation;
- private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
new RemoteCallback.OnResultListener() {
@@ -155,7 +160,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
setTriggerBack(false);
- onGestureFinished(false);
+ resetTouchTracker();
});
}
});
@@ -169,10 +174,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
- @NonNull BackAnimationBackground backAnimationBackground) {
- this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver(),
- backAnimationBackground);
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ this(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ ActivityTaskManager.getService(),
+ context,
+ context.getContentResolver(),
+ backAnimationBackground,
+ shellBackAnimationRegistry);
}
@VisibleForTesting
@@ -182,8 +195,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver,
- @NonNull BackAnimationBackground backAnimationBackground) {
+ Context context,
+ ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -197,11 +212,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
.setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
- }
-
- @VisibleForTesting
- void setEnableUAnimation(boolean enable) {
- IS_U_ANIMATION_ENABLED = enable;
+ mShellBackAnimationRegistry = shellBackAnimationRegistry;
}
private void onInit() {
@@ -209,26 +220,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
-
- initBackAnimationRunners();
- }
-
- private void initBackAnimationRunners() {
- if (!IS_U_ANIMATION_ENABLED) {
- return;
- }
-
- final CrossTaskBackAnimation crossTaskAnimation =
- new CrossTaskBackAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
- crossTaskAnimation.mBackAnimationRunner);
- mDefaultActivityAnimation =
- new CrossActivityAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- mCustomizeActivityAnimation =
- new CustomizeActivityAnimation(mContext, mAnimationBackground);
- // TODO (236760237): register dialog close animation when it's completed.
}
private void setupAnimationDeveloperSettingsObserver(
@@ -330,7 +321,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.registerAnimation(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner)));
+ new BackAnimationRunner(
+ callback,
+ runner,
+ controller.mContext,
+ CUJ_PREDICTIVE_BACK_HOME)));
}
@Override
@@ -359,11 +354,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
void registerAnimation(@BackNavigationInfo.BackTargetType int type,
@NonNull BackAnimationRunner runner) {
- mAnimationDefinition.set(type, runner);
+ mShellBackAnimationRegistry.registerAnimation(type, runner);
}
void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
- mAnimationDefinition.remove(type);
+ mShellBackAnimationRegistry.unregisterAnimation(type);
+ }
+
+ private TouchTracker getActiveTracker() {
+ if (mCurrentTracker.isActive()) return mCurrentTracker;
+ if (mQueuedTracker.isActive()) return mQueuedTracker;
+ return null;
}
/**
@@ -377,11 +378,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
float velocityY,
int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
- if (mPostCommitAnimationInProgress) {
+
+ TouchTracker activeTouchTracker = getActiveTracker();
+ if (activeTouchTracker != null) {
+ activeTouchTracker.update(touchX, touchY, velocityX, velocityY);
+ }
+
+ // two gestures are waiting to be processed at the moment, skip any further user touches
+ if (mCurrentTracker.isFinished() && mQueuedTracker.isFinished()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "Ignoring MotionEvent because two gestures are already being queued.");
return;
}
- mTouchTracker.update(touchX, touchY, velocityX, velocityY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -399,49 +408,61 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
if (keyAction == MotionEvent.ACTION_CANCEL) {
- mTriggerBack = false;
+ setTriggerBack(false);
}
- onGestureFinished(true);
+ onGestureFinished();
}
}
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.");
- finishBackNavigation();
+ TouchTracker touchTracker;
+ if (mCurrentTracker.isInInitialState()) {
+ touchTracker = mCurrentTracker;
+ } else if (mQueuedTracker.isInInitialState()) {
+ touchTracker = mQueuedTracker;
+ } else {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW,
+ "Cannot start tracking new gesture with neither tracker in initial state.");
+ return;
}
-
- mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
+ touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
+ touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE);
mBackGestureStarted = true;
+ if (touchTracker == mCurrentTracker) {
+ // Only start the back navigation if no other gesture is being processed. Otherwise,
+ // the back navigation will be started once the current gesture has finished.
+ startBackNavigation(mCurrentTracker);
+ }
+ }
+
+ private void startBackNavigation(@NonNull TouchTracker touchTracker) {
try {
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
- onBackNavigationInfoReceived(mBackNavigationInfo);
+ onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
- finishBackNavigation();
+ finishBackNavigation(touchTracker.getTriggerBack());
}
}
- private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) {
+ private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo,
+ @NonNull TouchTracker touchTracker) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
- Log.e(TAG, "Received BackNavigationInfo is null.");
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
return;
}
final int backType = backNavigationInfo.getType();
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
- if (mAnimationDefinition.contains(backType)) {
- mAnimationDefinition.get(backType).startGesture();
- } else {
+ if (!mShellBackAnimationRegistry.startGesture(backType)) {
mActiveCallback = null;
}
} else {
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
+ dispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
}
}
@@ -449,16 +470,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) {
return;
}
-
- final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
+ // Skip dispatching if the move corresponds to the queued instead of the current gesture
+ if (mQueuedTracker.isActive()) return;
+ final BackMotionEvent backEvent = mCurrentTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backEvent);
}
private void injectBackKey() {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "injectBackKey");
sendBackEvent(KeyEvent.ACTION_DOWN);
sendBackEvent(KeyEvent.ACTION_UP);
}
+ @SuppressLint("MissingPermission")
private void sendBackEvent(int action) {
final long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
@@ -469,7 +493,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ev.setDisplayId(mContext.getDisplay().getDisplayId());
if (!mContext.getSystemService(InputManager.class)
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
- Log.e(TAG, "Inject input event fail");
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Inject input event fail");
}
}
@@ -498,7 +522,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*
* @param callback the callback to be invoked when the animation ends.
*/
- private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) {
+ private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback,
+ @NonNull TouchTracker touchTracker) {
if (callback == null) {
return;
}
@@ -507,12 +532,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) {
- final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backMotionEvent = touchTracker.createProgressEvent();
if (backMotionEvent != null) {
// Constraints - absolute values
float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond();
float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond();
- float maxX = mTouchTracker.getMaxDistance(); // px
+ float maxX = touchTracker.getMaxDistance(); // px
float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px
// Current state
@@ -540,9 +565,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
animator.addUpdateListener(animation -> {
Float animatedValue = (Float) animation.getAnimatedValue();
- float progress = mTouchTracker.getProgress(animatedValue);
- final BackMotionEvent backEvent = mTouchTracker
- .createProgressEvent(progress);
+ float progress = touchTracker.getProgress(animatedValue);
+ final BackMotionEvent backEvent = touchTracker.createProgressEvent(
+ progress);
dispatchOnBackProgressed(mActiveCallback, backEvent);
});
@@ -601,27 +626,27 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
public void setTriggerBack(boolean triggerBack) {
- if (mPostCommitAnimationInProgress) {
- return;
+ TouchTracker activeBackGestureInfo = getActiveTracker();
+ if (activeBackGestureInfo != null) {
+ activeBackGestureInfo.setTriggerBack(triggerBack);
}
- mTriggerBack = triggerBack;
- mTouchTracker.setTriggerBack(triggerBack);
}
private void setSwipeThresholds(
float linearDistance,
float maxDistance,
float nonLinearFactor) {
- mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
+ mCurrentTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
+ mQueuedTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
}
- private void invokeOrCancelBack() {
+ private void invokeOrCancelBack(@NonNull TouchTracker touchTracker) {
// Make a synchronized call to core before dispatch back event to client side.
// If the close transition happens before the core receives onAnimationFinished, there will
// play a second close animation for that transition.
if (mBackAnimationFinishedCallback != null) {
try {
- mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
+ mBackAnimationFinishedCallback.onAnimationFinished(touchTracker.getTriggerBack());
} catch (RemoteException e) {
Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
}
@@ -630,30 +655,31 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mBackNavigationInfo != null) {
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
- dispatchOrAnimateOnBackInvoked(callback);
+ if (touchTracker.getTriggerBack()) {
+ dispatchOrAnimateOnBackInvoked(callback, touchTracker);
} else {
dispatchOnBackCancelled(callback);
}
}
- finishBackNavigation();
+ finishBackNavigation(touchTracker.getTriggerBack());
}
/**
* Called when the gesture is released, then it could start the post commit animation.
*/
- private void onGestureFinished(boolean fromTouch) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted) {
- finishBackNavigation();
+ private void onGestureFinished() {
+ TouchTracker activeTouchTracker = getActiveTracker();
+ if (!mBackGestureStarted || activeTouchTracker == null) {
+ // This can happen when an unfinished gesture has been reset in resetTouchTracker
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "onGestureFinished called while no gesture is started");
return;
}
+ boolean triggerBack = activeTouchTracker.getTriggerBack();
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", triggerBack);
- if (fromTouch) {
- // Let touch reset the flag otherwise it will start a new back navigation and refresh
- // the info when received a new move event.
- mBackGestureStarted = false;
- }
+ mBackGestureStarted = false;
+ activeTouchTracker.setState(TouchTracker.TouchTrackerState.FINISHED);
if (mPostCommitAnimationInProgress) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
@@ -662,30 +688,33 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mBackNavigationInfo == null) {
// No focus window found or core are running recents animation, inject back key as
- // legacy behavior.
- if (mTriggerBack) {
+ // legacy behavior, or new back gesture was started while previous has not finished yet
+ if (!mQueuedTracker.isInInitialState()) {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW, "mBackNavigationInfo is null AND there is "
+ + "another back animation in progress");
+ }
+ mCurrentTracker.reset();
+ if (triggerBack) {
injectBackKey();
}
- finishBackNavigation();
+ finishBackNavigation(triggerBack);
return;
}
final int backType = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
// Simply trigger and finish back navigation when no animator defined.
- if (!shouldDispatchToAnimator() || runner == null) {
- invokeOrCancelBack();
+ if (!shouldDispatchToAnimator()
+ || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Trigger back without dispatching to animator.");
+ invokeOrCancelBack(mCurrentTracker);
+ mCurrentTracker.reset();
return;
- }
- if (runner.isWaitingAnimation()) {
+ } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
// Supposed it is in post commit animation state, and start the timeout to watch
// if the animation is ready.
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
return;
- } else if (runner.isAnimationCancelled()) {
- invokeOrCancelBack();
- return;
}
startPostCommitAnimation();
}
@@ -705,8 +734,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
// The next callback should be {@link #onBackAnimationFinished}.
- if (mTriggerBack) {
- dispatchOrAnimateOnBackInvoked(mActiveCallback);
+ if (mCurrentTracker.getTriggerBack()) {
+ dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker);
} else {
dispatchOnBackCancelled(mActiveCallback);
}
@@ -724,112 +753,150 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
- // Trigger the real back.
- invokeOrCancelBack();
+ if (mCurrentTracker.isActive() || mCurrentTracker.isFinished()) {
+ // Trigger the real back.
+ invokeOrCancelBack(mCurrentTracker);
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "mCurrentBackGestureInfo was null when back animation finished");
+ }
+ resetTouchTracker();
+ }
+
+ /**
+ * Resets the TouchTracker and potentially starts a new back navigation in case one is queued
+ */
+ private void resetTouchTracker() {
+ TouchTracker temp = mCurrentTracker;
+ mCurrentTracker = mQueuedTracker;
+ temp.reset();
+ mQueuedTracker = temp;
+
+ if (mCurrentTracker.isInInitialState()) {
+ if (mBackGestureStarted) {
+ mBackGestureStarted = false;
+ dispatchOnBackCancelled(mActiveCallback);
+ finishBackNavigation(false);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "resetTouchTracker -> reset an unfinished gesture");
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> no queued gesture");
+ }
+ return;
+ }
+
+ if (mCurrentTracker.isFinished() && mCurrentTracker.getTriggerBack()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> start queued back navigation "
+ + "AND post commit animation");
+ injectBackKey();
+ finishBackNavigation(true);
+ mCurrentTracker.reset();
+ } else if (!mCurrentTracker.isFinished()) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+ "resetTouchTracker -> queued gesture not finished; do nothing");
+ } else {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> reset queued gesture");
+ mCurrentTracker.reset();
+ }
}
/**
* This should be called after the whole back navigation is completed.
*/
@VisibleForTesting
- void finishBackNavigation() {
+ void finishBackNavigation(boolean triggerBack) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
- mShouldStartOnNextMoveEvent = false;
- mTouchTracker.reset();
mActiveCallback = null;
- // reset to default
- if (mDefaultActivityAnimation != null
- && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- }
+ mShellBackAnimationRegistry.resetDefaultCrossActivity();
if (mBackNavigationInfo != null) {
- mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+ mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
}
- mTriggerBack = false;
}
- private BackAnimationRunner getAnimationRunnerAndInit() {
- int type = mBackNavigationInfo.getType();
- // Initiate customized cross-activity animation, or fall back to cross activity animation
- if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
- final BackNavigationInfo.CustomAnimationInfo animationInfo =
- mBackNavigationInfo.getCustomAnimationInfo();
- if (animationInfo != null && mCustomizeActivityAnimation != null
- && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
- mAnimationDefinition.get(type).resetWaitingAnimation();
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mCustomizeActivityAnimation.mBackAnimationRunner);
- }
- }
- return mAnimationDefinition.get(type);
- }
private void createAdapter() {
- IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IBackAnimationFinishedCallback finishedCallback) {
- mShellExecutor.execute(() -> {
- if (mBackNavigationInfo == null) {
- Log.e(TAG, "Lack of navigation info to start animation.");
- return;
+ IBackAnimationRunner runner =
+ new IBackAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IBackAnimationFinishedCallback finishedCallback) {
+ mShellExecutor.execute(
+ () -> {
+ if (mBackNavigationInfo == null) {
+ ProtoLog.e(WM_SHELL_BACK_PREVIEW,
+ "Lack of navigation info to start animation.");
+ return;
+ }
+ final BackAnimationRunner runner =
+ mShellBackAnimationRegistry.getAnimationRunnerAndInit(
+ mBackNavigationInfo);
+ if (runner == null) {
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished(false);
+ } catch (RemoteException e) {
+ Log.w(
+ TAG,
+ "Failed call IBackNaviAnimationController",
+ e);
+ }
+ }
+ return;
+ }
+ mActiveCallback = runner.getCallback();
+ mBackAnimationFinishedCallback = finishedCallback;
+
+ ProtoLog.d(
+ WM_SHELL_BACK_PREVIEW,
+ "BackAnimationController: startAnimation()");
+ runner.startAnimation(
+ apps,
+ wallpapers,
+ nonApps,
+ () ->
+ mShellExecutor.execute(
+ BackAnimationController.this
+ ::onBackAnimationFinished));
+
+ if (apps.length >= 1) {
+ dispatchOnBackStarted(
+ mActiveCallback,
+ mCurrentTracker.createStartEvent(apps[0]));
+ }
+
+ // Dispatch the first progress after animation start for
+ // smoothing the initial animation, instead of waiting for next
+ // onMove.
+ final BackMotionEvent backFinish = mCurrentTracker
+ .createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation
+ // start, we have to trigger the uninterruptible transition
+ // to finish the back animation.
+ startPostCommitAnimation();
+ }
+ });
}
- final int type = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = getAnimationRunnerAndInit();
- if (runner == null) {
- Log.e(TAG, "Animation didn't be defined for type "
- + BackNavigationInfo.typeToString(type));
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished(false);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call IBackNaviAnimationController", e);
- }
- }
- return;
- }
- mActiveCallback = runner.getCallback();
- mBackAnimationFinishedCallback = finishedCallback;
-
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
- runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
- BackAnimationController.this::onBackAnimationFinished));
- if (apps.length >= 1) {
- dispatchOnBackStarted(
- mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+ @Override
+ public void onAnimationCancelled() {
+ mShellExecutor.execute(
+ () -> {
+ if (!mShellBackAnimationRegistry.cancel(
+ mBackNavigationInfo.getType())) {
+ return;
+ }
+ if (!mBackGestureStarted) {
+ invokeOrCancelBack(mCurrentTracker);
+ }
+ });
}
-
- // Dispatch the first progress after animation start for smoothing the initial
- // animation, instead of waiting for next onMove.
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
- // if the down -> up gesture happened before animation start, we have to
- // trigger the uninterruptible transition to finish the back animation.
- startPostCommitAnimation();
- }
- });
- }
-
- @Override
- public void onAnimationCancelled() {
- mShellExecutor.execute(() -> {
- final BackAnimationRunner runner = mAnimationDefinition.get(
- mBackNavigationInfo.getType());
- if (runner == null) {
- return;
- }
- runner.cancelAnimation();
- if (!mBackGestureStarted) {
- invokeOrCancelBack();
- }
- });
- }
- };
+ };
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 913239f74bf2..dc413b059fd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.annotation.NonNull;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@ import android.view.RemoteAnimationTarget;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
-class BackAnimationRunner {
+public class BackAnimationRunner {
+ private static final int NO_CUJ = -1;
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
+ private final @InteractionJankMonitor.CujType int mCujType;
+ private final Context mContext;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -44,10 +51,22 @@ class BackAnimationRunner {
/** True when the back animation is cancelled */
private boolean mAnimationCancelled;
- BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
- @NonNull IRemoteAnimationRunner runner) {
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context,
+ @InteractionJankMonitor.CujType int cujType) {
mCallback = callback;
mRunner = runner;
+ mCujType = cujType;
+ mContext = context;
+ }
+
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context) {
+ this(callback, runner, context, NO_CUJ);
}
/** Returns the registered animation runner */
@@ -70,10 +89,17 @@ class BackAnimationRunner {
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.endTracing(mCujType);
+ }
finishedCallback.run();
}
};
mWaitingAnimation = false;
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.beginTracing(
+ mCujType, mContext, apps[0].leash, /* tag */ null);
+ }
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
@@ -82,6 +108,10 @@ class BackAnimationRunner {
}
}
+ private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ return apps.length > 0 && mCujType != NO_CUJ;
+ }
+
void startGesture() {
mWaitingAnimation = true;
mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 74a243d34642..24479d7b5f39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -51,9 +52,11 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/** Class that defines cross-activity animation. */
@ShellMainThread
-class CrossActivityAnimation {
+public class CrossActivityAnimation extends ShellBackAnimation {
/**
* Minimum scale of the entering/closing window.
*/
@@ -106,6 +109,7 @@ class CrossActivityAnimation {
private final SpringAnimation mLeavingProgressSpring;
// Max window x-shift in pixels.
private final float mWindowXShift;
+ private final BackAnimationRunner mBackAnimationRunner;
private float mEnteringProgress = 0f;
private float mLeavingProgress = 0f;
@@ -126,13 +130,14 @@ class CrossActivityAnimation {
private IRemoteAnimationFinishedCallback mFinishCallback;
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
- CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossActivityAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mBackground = background;
mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
mEnteringProgressSpring.setSpring(new SpringForce()
@@ -357,6 +362,11 @@ class CrossActivityAnimation {
mTransaction.apply();
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a7dd27a0784f..fc5ff017ebe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -20,6 +20,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.window.BackEvent.EDGE_RIGHT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -47,21 +48,23 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/**
* Controls the animation of swiping back and returning to another task.
*
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the closing and entering app windows.
- * Once the gesture is committed, the second part remains the closing window in place.
- * The entering window plays the rest of app opening transition to enter full screen.
+ * <p>This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows. Once the gesture is committed, the second
+ * part remains the closing window in place. The entering window plays the rest of app opening
+ * transition to enter full screen.
*
- * This animation is used only for apps that enable back dispatching via
- * {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
- * navigation to launcher starts.
+ * <p>This animation is used only for apps that enable back dispatching via {@link
+ * android.window.OnBackInvokedDispatcher}. The controller registers an {@link
+ * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
+ * launcher starts.
*/
@ShellMainThread
-class CrossTaskBackAnimation {
+public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int BACKGROUNDCOLOR = 0x43433A;
/**
@@ -104,28 +107,44 @@ class CrossTaskBackAnimation {
private final float[] mTmpFloat9 = new float[9];
private final float[] mTmpTranslate = {0, 0, 0};
-
+ private final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationBackground mBackground;
+ private final Context mContext;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
private boolean mBackInProgress = false;
-
private boolean mIsRightEdge;
private float mProgress = 0;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
- private final BackAnimationBackground mBackground;
-
- CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ mContext = context;
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
}
+ private static void computeScaleTransformMatrix(float scale, float[] matrix) {
+ matrix[0] = scale;
+ matrix[1] = 0;
+ matrix[2] = 0;
+ matrix[3] = 0;
+ matrix[4] = scale;
+ matrix[5] = 0;
+ matrix[6] = 0;
+ matrix[7] = 0;
+ matrix[8] = scale;
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
private float getInterpolatedProgress(float backProgress) {
return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
}
@@ -233,18 +252,6 @@ class CrossTaskBackAnimation {
mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
}
- static void computeScaleTransformMatrix(float scale, float[] matrix) {
- matrix[0] = scale;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = scale;
- matrix[5] = 0;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = scale;
- }
-
private void finishAnimation() {
if (mEnteringTarget != null) {
mEnteringTarget.leash.release();
@@ -314,11 +321,12 @@ class CrossTaskBackAnimation {
valueAnimator.start();
}
- private static float mapRange(float value, float min, float max) {
- return min + (value * (max - min));
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
}
- private final class Callback extends IOnBackInvokedCallback.Default {
+ private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
@@ -340,7 +348,7 @@ class CrossTaskBackAnimation {
mProgressAnimator.reset();
onGestureCommitted();
}
- };
+ }
private final class Runner extends IRemoteAnimationRunner.Default {
@Override
@@ -360,5 +368,5 @@ class CrossTaskBackAnimation {
startBackAnimation();
mFinishCallback = finishedCallback;
}
- };
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 2d6ec7547187..5254ff466123 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -55,13 +56,13 @@ import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
-/**
- * Class that handle customized close activity transition animation.
- */
+import javax.inject.Inject;
+
+/** Class that handle customized close activity transition animation. */
@ShellMainThread
-class CustomizeActivityAnimation {
+public class CustomizeActivityAnimation extends ShellBackAnimation {
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationRunner mBackAnimationRunner;
private final float mCornerRadius;
private final SurfaceControl.Transaction mTransaction;
private final BackAnimationBackground mBackground;
@@ -88,7 +89,8 @@ class CustomizeActivityAnimation {
private final Choreographer mChoreographer;
- CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
this(context, background, new SurfaceControl.Transaction(), null);
}
@@ -96,7 +98,8 @@ class CustomizeActivityAnimation {
SurfaceControl.Transaction transaction, Choreographer choreographer) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mCustomAnimationLoader = new CustomAnimationLoader(context);
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
@@ -258,10 +261,12 @@ class CustomizeActivityAnimation {
valueAnimator.start();
}
- /**
- * Load customize animation before animation start.
- */
- boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ /** Load customize animation before animation start. */
+ @Override
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ if (animationInfo == null) {
+ return false;
+ }
final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
if (result != null) {
mCloseAnimation = result.mCloseAnimation;
@@ -272,6 +277,11 @@ class CustomizeActivityAnimation {
return false;
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
new file mode 100644
index 000000000000..312e88db863e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.window.BackNavigationInfo;
+
+import javax.inject.Qualifier;
+
+/** Base class for all back animations. */
+public abstract class ShellBackAnimation {
+ @Qualifier
+ public @interface CrossActivity {}
+
+ @Qualifier
+ public @interface CrossTask {}
+
+ @Qualifier
+ public @interface CustomizeActivity {}
+
+ @Qualifier
+ public @interface ReturnToHome {}
+
+ /** Retrieve the {@link BackAnimationRunner} associated with this animation. */
+ public abstract BackAnimationRunner getRunner();
+
+ /**
+ * Prepare the next animation with customized animation.
+ *
+ * @return true if this type of back animation should override the default.
+ */
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
new file mode 100644
index 000000000000..62b18f342995
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.window.BackNavigationInfo;
+
+/** Registry for all types of default back animations */
+public class ShellBackAnimationRegistry {
+ private static final String TAG = "ShellBackPreview";
+
+ private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+ private final ShellBackAnimation mDefaultCrossActivityAnimation;
+ private final ShellBackAnimation mCustomizeActivityAnimation;
+
+ public ShellBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
+ @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation,
+ @ShellBackAnimation.CustomizeActivity @Nullable
+ ShellBackAnimation customizeActivityAnimation,
+ @ShellBackAnimation.ReturnToHome @Nullable
+ ShellBackAnimation defaultBackToHomeAnimation) {
+ if (crossActivityAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner());
+ }
+ if (crossActivityAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner());
+ }
+ if (defaultBackToHomeAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner());
+ }
+
+ mDefaultCrossActivityAnimation = crossActivityAnimation;
+ mCustomizeActivityAnimation = customizeActivityAnimation;
+
+ // TODO(b/236760237): register dialog close animation when it's completed.
+ }
+
+ void registerAnimation(
+ @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
+ }
+
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
+ }
+
+ /**
+ * Start the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean startGesture(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.startGesture();
+ return true;
+ }
+
+ /**
+ * Cancel the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean cancel(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.cancelAnimation();
+ return true;
+ }
+
+ boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return true;
+ }
+ return runner.isAnimationCancelled();
+ }
+
+ boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ return runner.isWaitingAnimation();
+ }
+
+ void resetDefaultCrossActivity() {
+ if (mDefaultCrossActivityAnimation == null
+ || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ return;
+ }
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
+ }
+
+ BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
+ int type = backNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ if (mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(
+ backNavigationInfo.getCustomAnimationInfo())) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.getRunner());
+ }
+ }
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ Log.e(
+ TAG,
+ "Animation didn't be defined for type "
+ + BackNavigationInfo.typeToString(type));
+ }
+ return runner;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
index 837d5ff3b073..f02559f36169 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -12,19 +12,19 @@
]
},
{
- "name": "CtsWindowManagerDeviceTestCases",
+ "name": "CtsWindowManagerDeviceBackNavigation",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
- "include-filter": "android.server.wm.BackGestureInvokedTest"
+ "include-filter": "android.server.wm.backnavigation.BackGestureInvokedTest"
},
{
- "include-filter": "android.server.wm.BackNavigationTests"
+ "include-filter": "android.server.wm.backnavigation.BackNavigationTests"
},
{
- "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+ "include-filter": "android.server.wm.backnavigation.OnBackInvokedCallbackGestureTest"
}
]
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index a0ada39b459e..8a59a9f62425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -52,6 +52,7 @@ class TouchTracker {
private float mStartThresholdX;
private int mSwipeEdge;
private boolean mCancelled;
+ private TouchTrackerState mState = TouchTrackerState.INITIAL;
void update(float touchX, float touchY, float velocityX, float velocityY) {
/**
@@ -76,6 +77,26 @@ class TouchTracker {
mTriggerBack = triggerBack;
}
+ boolean getTriggerBack() {
+ return mTriggerBack;
+ }
+
+ void setState(TouchTrackerState state) {
+ mState = state;
+ }
+
+ boolean isInInitialState() {
+ return mState == TouchTrackerState.INITIAL;
+ }
+
+ boolean isActive() {
+ return mState == TouchTrackerState.ACTIVE;
+ }
+
+ boolean isFinished() {
+ return mState == TouchTrackerState.FINISHED;
+ }
+
void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
mInitTouchX = touchX;
mInitTouchY = touchY;
@@ -89,6 +110,7 @@ class TouchTracker {
mStartThresholdX = 0;
mCancelled = false;
mTriggerBack = false;
+ mState = TouchTrackerState.INITIAL;
mSwipeEdge = BackEvent.EDGE_LEFT;
}
@@ -186,4 +208,9 @@ class TouchTracker {
mMaxDistance = maxDistance;
mNonLinearFactor = nonLinearFactor;
}
+
+ enum TouchTrackerState {
+ INITIAL, ACTIVE, FINISHED
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index f1ee8fa38485..a67821b7e819 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -318,7 +318,7 @@ public class BadgedImageView extends ConstraintLayout {
/**
* Animates the dot to the given scale, running the optional callback when the animation ends.
*/
- private void animateDotScale(float toScale, @Nullable Runnable after) {
+ public void animateDotScale(float toScale, @Nullable Runnable after) {
mDotIsAnimating = true;
// Don't restart the animation if we're already animating to the given value.
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 9a2b81243861..85ea8097a2c1 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
@@ -973,9 +973,9 @@ public class Bubble implements BubbleViewProvider {
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
pw.print(" isDismissable: "); pw.println(mIsDismissable);
- pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
+ pw.println(" bubbleMetadataFlagListener null?: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
- mExpandedView.dump(pw);
+ mExpandedView.dump(pw, " ");
}
}
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 8400ddec0af5..f0da35df39ee 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.bubbles;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -57,6 +56,7 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
@@ -115,6 +115,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -143,6 +144,8 @@ public class BubbleController implements ConfigurationChangeListener,
// Should match with PhoneWindowManager
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
/**
* Common interface to send updates to bubble views.
@@ -182,6 +185,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
private final TaskViewTransitions mTaskViewTransitions;
+ private final Transitions mTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
@@ -282,6 +286,7 @@ public class BubbleController implements ConfigurationChangeListener,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
BubbleProperties bubbleProperties) {
@@ -312,11 +317,13 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
+ mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
@@ -416,23 +423,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
}, mMainHandler);
- mTaskStackListener.addListener(new TaskStackListenerCallback() {
- @Override
- public void onTaskMovedToFront(int taskId) {
- mMainExecutor.execute(() -> {
- int expandedId = INVALID_TASK_ID;
- if (mStackView != null && mStackView.getExpandedBubble() != null
- && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mStackView.isSwitchAnimating()) {
- expandedId = mStackView.getExpandedBubble().getTaskId();
- }
- if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
- mBubbleData.setExpanded(false);
- }
- });
- }
+ mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
@@ -520,6 +513,7 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
*/
public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+ mBubbleProperties.refresh();
if (canShowAsBubbleBar() && listener != null) {
// Only set the listener if we can show the bubble bar.
mBubbleStateListener = listener;
@@ -537,6 +531,7 @@ public class BubbleController implements ConfigurationChangeListener,
* will be updated accordingly.
*/
public void unregisterBubbleStateListener() {
+ mBubbleProperties.refresh();
if (mBubbleStateListener != null) {
mBubbleStateListener = null;
setUpBubbleViewsForMode();
@@ -883,8 +878,10 @@ public class BubbleController implements ConfigurationChangeListener,
String action = intent.getAction();
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
- if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+ boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)
+ || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)
+ || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason);
+ if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse)
|| Intent.ACTION_SCREEN_OFF.equals(action)) {
mMainExecutor.execute(() -> collapseStack());
}
@@ -953,7 +950,8 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
@@ -992,7 +990,8 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
@@ -1070,6 +1069,15 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ if (mLayerView == null) return;
+ mLayerView.showUserEducation(position);
+ }
+
@VisibleForTesting
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1122,6 +1130,16 @@ public class BubbleController implements ConfigurationChangeListener,
}
/**
+ * Expands the stack if the selected bubble is present. This is currently used when user
+ * education view is clicked to expand the selected bubble.
+ */
+ public void expandStackWithSelectedBubble() {
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleData.setExpanded(true);
+ }
+ }
+
+ /**
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow. This is currently used when opening a bubble via clicking on a conversation widget.
*/
@@ -1737,7 +1755,8 @@ public class BubbleController implements ConfigurationChangeListener,
+ " expandedChanged=" + update.expandedChanged
+ " selectionChanged=" + update.selectionChanged
+ " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null));
+ + " unsuppressed=" + (update.unsuppressedBubble != null)
+ + " shouldShowEducation=" + update.shouldShowEducation);
}
ensureBubbleViewsAndWindowCreated();
@@ -1961,6 +1980,15 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ /**
+ * Returns whether the stack is animating or not.
+ */
+ public boolean isStackAnimating() {
+ return mStackView != null
+ && (mStackView.isExpansionAnimating()
+ || mStackView.isSwitchAnimating());
+ }
+
@VisibleForTesting
@Nullable
public BubbleStackView getStackView() {
@@ -1989,13 +2017,20 @@ public class BubbleController implements ConfigurationChangeListener,
* Description of current bubble state.
*/
private void dump(PrintWriter pw, String prefix) {
- pw.println("BubbleController state:");
+ pw.print(prefix); pw.println("BubbleController state:");
+ pw.print(prefix); pw.println(" currentUserId= " + mCurrentUserId);
+ pw.print(prefix); pw.println(" isStatusBarShade= " + mIsStatusBarShade);
+ pw.print(prefix); pw.println(" isShowingAsBubbleBar= " + isShowingAsBubbleBar());
+ pw.println();
+
mBubbleData.dump(pw);
pw.println();
+
if (mStackView != null) {
mStackView.dump(pw);
}
pw.println();
+
mImpl.mCachedState.dump(pw);
}
@@ -2146,6 +2181,12 @@ public class BubbleController implements ConfigurationChangeListener,
public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
}
+
+ @Override
+ public void showUserEducation(int positionX, int positionY) {
+ mMainExecutor.execute(() ->
+ mController.showUserEducation(new Point(positionX, positionY)));
+ }
}
private class BubblesImpl implements Bubbles {
@@ -2244,8 +2285,7 @@ public class BubbleController implements ConfigurationChangeListener,
pw.println("mIsStackExpanded: " + mIsStackExpanded);
pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey);
- pw.print("mSuppressedBubbleKeys: ");
- pw.println(mSuppressedBubbleKeys.size());
+ pw.println("mSuppressedBubbleKeys: " + mSuppressedBubbleKeys.size());
for (String key : mSuppressedBubbleKeys) {
pw.println(" suppressing: " + key);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index cc8f50e09fcb..595a4afbfc86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -77,6 +77,7 @@ public class BubbleData {
boolean orderChanged;
boolean suppressedSummaryChanged;
boolean expanded;
+ boolean shouldShowEducation;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@@ -126,6 +127,7 @@ public class BubbleData {
bubbleBarUpdate.expandedChanged = expandedChanged;
bubbleBarUpdate.expanded = expanded;
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
if (selectionChanged) {
bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
? selectedBubble.getKey()
@@ -165,6 +167,7 @@ public class BubbleData {
*/
BubbleBarUpdate getInitialState() {
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
for (int i = 0; i < bubbles.size(); i++) {
bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
}
@@ -187,6 +190,7 @@ public class BubbleData {
private final Context mContext;
private final BubblePositioner mPositioner;
+ private final BubbleEducationController mEducationController;
private final Executor mMainExecutor;
/** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
@@ -233,10 +237,11 @@ public class BubbleData {
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
- Executor mainExecutor) {
+ BubbleEducationController educationController, Executor mainExecutor) {
mContext = context;
mLogger = bubbleLogger;
mPositioner = positioner;
+ mEducationController = educationController;
mMainExecutor = mainExecutor;
mOverflow = new BubbleOverflow(context, positioner);
mBubbles = new ArrayList<>();
@@ -447,6 +452,7 @@ public class BubbleData {
if (bubble.shouldAutoExpand()) {
bubble.setShouldAutoExpand(false);
setSelectedBubbleInternal(bubble);
+
if (!mExpanded) {
setExpandedInternal(true);
}
@@ -877,6 +883,9 @@ public class BubbleData {
private void dispatchPendingChanges() {
if (mListener != null && mStateChange.anythingChanged()) {
+ mStateChange.shouldShowEducation = mSelectedBubble != null
+ && mEducationController.shouldShowStackEducation(mSelectedBubble)
+ && !mExpanded;
mListener.applyUpdate(mStateChange);
}
mStateChange = new Update(mBubbles, mOverflowBubbles);
@@ -1231,29 +1240,30 @@ public class BubbleData {
* Description of current bubble data state.
*/
public void dump(PrintWriter pw) {
- pw.print("selected: ");
+ pw.println("BubbleData state:");
+ pw.print(" selected: ");
pw.println(mSelectedBubble != null
? mSelectedBubble.getKey()
: "null");
- pw.print("expanded: ");
+ pw.print(" expanded: ");
pw.println(mExpanded);
- pw.print("stack bubble count: ");
+ pw.print("Stack bubble count: ");
pw.println(mBubbles.size());
for (Bubble bubble : mBubbles) {
bubble.dump(pw);
}
- pw.print("overflow bubble count: ");
+ pw.print("Overflow bubble count: ");
pw.println(mOverflowBubbles.size());
for (Bubble bubble : mOverflowBubbles) {
bubble.dump(pw);
}
- pw.print("summaryKeys: ");
+ pw.print("SummaryKeys: ");
pw.println(mSuppressedGroupKeys.size());
for (String key : mSuppressedGroupKeys.keySet()) {
- pw.println(" suppressing: " + key);
+ pw.println(" suppressing: " + key);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 76662c47238f..f56b1712c5c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -44,7 +44,7 @@ public class BubbleDebugConfig {
static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
static final boolean DEBUG_EXPERIMENTS = true;
static final boolean DEBUG_OVERFLOW = false;
- static final boolean DEBUG_USER_EDUCATION = false;
+ public static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
@@ -77,20 +77,25 @@ public class BubbleDebugConfig {
static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) {
StringBuilder sb = new StringBuilder();
- for (Bubble bubble : bubbles) {
+ for (int i = 0; i < bubbles.size(); i++) {
+ Bubble bubble = bubbles.get(i);
if (bubble == null) {
- sb.append(" <null> !!!!!\n");
+ sb.append(" <null> !!!!!");
} else {
boolean isSelected = (selected != null
- && selected.getKey() != BubbleOverflow.KEY
+ && !BubbleOverflow.KEY.equals(selected.getKey())
&& bubble == selected);
String arrow = isSelected ? "=>" : " ";
- sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n",
+
+ sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}",
arrow,
bubble.getLastActivity(),
(bubble.showInShade() ? 1 : 0),
bubble.getKey()));
}
+ if (i != bubbles.size() - 1) {
+ sb.append("\n");
+ }
}
return sb.toString();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e6986012dd88..0568edaa7ab6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -949,7 +949,9 @@ public class BubbleExpandedView extends LinearLayout {
if (mTaskView != null
&& mTaskView.getVisibility() == VISIBLE
&& mTaskView.isAttachedToWindow()) {
- mTaskView.onLocationChanged();
+ // post this to the looper, because if the device orientation just changed, we need to
+ // let the current shell transition complete before updating the task view bounds.
+ post(() -> mTaskView.onLocationChanged());
}
if (mIsOverflow) {
// post this to the looper so that the view has a chance to be laid out before it can
@@ -1094,9 +1096,9 @@ public class BubbleExpandedView extends LinearLayout {
/**
* Description of current expanded view state.
*/
- public void dump(@NonNull PrintWriter pw) {
- pw.print("BubbleExpandedView");
- pw.print(" taskId: "); pw.println(mTaskId);
- pw.print(" stackView: "); pw.println(mStackView);
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.print(prefix); pw.println("BubbleExpandedView:");
+ pw.print(prefix); pw.print(" taskId: "); pw.println(mTaskId);
+ pw.print(prefix); pw.print(" stackView: "); pw.println(mStackView);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index df19757203eb..22e836aacfc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -27,6 +27,7 @@ import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.InsetDrawable
import android.util.PathParser
import android.view.LayoutInflater
+import android.view.View.VISIBLE
import android.widget.FrameLayout
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
@@ -112,7 +113,7 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(R.color.important_conversation),
+ res.getColor(com.android.launcher3.icons.R.color.important_conversation),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
@@ -156,7 +157,9 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
fun setShowDot(show: Boolean) {
showDot = show
- overflowBtn?.updateDotVisibility(true /* animate */)
+ if (overflowBtn?.visibility == VISIBLE) {
+ overflowBtn?.updateDotVisibility(true /* animate */)
+ }
}
/** Creates the expanded view for bubbles showing in the stack view. */
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 ea7053d8ee49..17e06e93b3a8 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
@@ -54,10 +54,6 @@ public class BubblePositioner {
public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
/** The max percent of screen width to use for the flyout on phone. */
public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
/** The percent of screen width for the expanded view on a small tablet. **/
private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
/** The percent of screen width for the expanded view when shown in the bubble bar. **/
@@ -95,6 +91,7 @@ public class BubblePositioner {
private int mPointerWidth;
private int mPointerHeight;
private int mPointerOverlap;
+ private int mManageButtonHeightIncludingMargins;
private int mManageButtonHeight;
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
@@ -176,21 +173,20 @@ public class BubblePositioner {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
} else {
- mExpandedViewLargeScreenWidth = isLandscape()
- ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
- : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+ mExpandedViewLargeScreenWidth =
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
}
if (mIsLargeScreen) {
- if (isLandscape() && !mIsSmallTablet) {
+ if (mIsSmallTablet) {
+ final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+ mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+ mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+ } else {
mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
R.dimen.bubble_expanded_view_largescreen_landscape_padding);
mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
- mExpandedViewLargeScreenInsetClosestEdge
- mExpandedViewLargeScreenWidth;
- } else {
- final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
- mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
- mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
}
} else {
mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
@@ -202,7 +198,9 @@ public class BubblePositioner {
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
- mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mManageButtonHeightIncludingMargins =
+ res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize(
@@ -420,7 +418,7 @@ public class BubblePositioner {
int pointerSize = showBubblesVertically()
? mPointerWidth
: (mPointerHeight + mPointerMargin);
- int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
return getAvailableRect().height()
- expandedContainerY
- paddingTop
@@ -438,6 +436,15 @@ public class BubblePositioner {
// overflow in landscape on phone is max
return MAX_HEIGHT;
}
+
+ if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) {
+ // the expanded view height on large tablets is calculated based on the shortest screen
+ // size and is the same in both portrait and landscape
+ int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
+ int shortestScreenSide = Math.min(mScreenRect.height(), mScreenRect.width());
+ return shortestScreenSide - 2 * maxVerticalInset - mManageButtonHeight;
+ }
+
float desiredHeight = isOverflow
? mOverflowHeight
: ((Bubble) bubble).getDesiredHeight(mContext);
@@ -466,7 +473,8 @@ public class BubblePositioner {
return topAlignment;
}
// If we're here, we're showing vertically & developer has made height less than maximum.
- int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ int manageButtonHeight =
+ isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
float pointerPosition = getPointerPosition(bubblePosition);
float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
float topIfCentered = pointerPosition - (expandedViewHeight / 2);
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 52c9bf8462ec..9402d028ecc4 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
@@ -25,6 +25,7 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_
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.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
@@ -56,6 +57,7 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
+import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -131,8 +133,6 @@ public class BubbleStackView extends FrameLayout
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
- private static final float SCRIM_ALPHA = 0.32f;
-
/** Minimum alpha value for scrim when alpha is being changed via drag */
private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
@@ -213,7 +213,8 @@ public class BubbleStackView extends FrameLayout
private ExpandedViewAnimationController mExpandedViewAnimationController;
private View mScrim;
- private boolean mScrimAnimating;
+ @Nullable
+ private ViewPropertyAnimator mScrimAnimation;
private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
@@ -305,8 +306,7 @@ public class BubbleStackView extends FrameLayout
String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
getBubblesOnScreen(), getExpandedBubble());
- pw.print(" stack visibility : "); pw.println(getVisibility());
- pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen);
+ pw.println(" bubbles on screen: "); pw.println(bubblesOnScreen);
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
@@ -314,7 +314,8 @@ public class BubbleStackView extends FrameLayout
pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha());
pw.print(" expandedContainerMatrix: ");
pw.println(mExpandedViewContainer.getAnimationMatrix());
-
+ pw.print(" stack visibility : "); pw.println(getVisibility());
+ pw.print(" temporarilyInvisible: "); pw.println(mTemporarilyInvisible);
mStackAnimationController.dump(pw);
mExpandedAnimationController.dump(pw);
@@ -749,8 +750,8 @@ public class BubbleStackView extends FrameLayout
float collapsed = -Math.min(dy, 0);
mExpandedViewAnimationController.updateDrag((int) collapsed);
- // Update scrim
- if (!mScrimAnimating) {
+ // Update scrim if it's not animating already
+ if (mScrimAnimation == null) {
mScrim.setAlpha(getScrimAlphaForDrag(collapsed));
}
}
@@ -769,8 +770,8 @@ public class BubbleStackView extends FrameLayout
} else {
mExpandedViewAnimationController.animateBackToExpanded();
- // Update scrim
- if (!mScrimAnimating) {
+ // Update scrim if it's not animating already
+ if (mScrimAnimation == null) {
showScrim(true, null /* runnable */);
}
}
@@ -779,14 +780,15 @@ public class BubbleStackView extends FrameLayout
private float getScrimAlphaForDrag(float dragAmount) {
// dragAmount should be negative as we allow scroll up only
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
+ float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
float dragFraction = dragAmount / dragMax;
- return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG);
+ return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction,
+ MIN_SCRIM_ALPHA_FOR_DRAG);
}
- return SCRIM_ALPHA;
+ return BUBBLE_EXPANDED_SCRIM_ALPHA;
}
};
@@ -1010,15 +1012,15 @@ public class BubbleStackView extends FrameLayout
}
if (mIsExpanded) {
- // Re-draw bubble row and pointer for new orientation.
- beforeExpandedViewAnimation();
+ // update the expanded view and pointer location for the new orientation.
+ hideFlyoutImmediate();
+ mExpandedViewContainer.setAlpha(0f);
+ updateExpandedView();
updateOverflowVisibility();
- updatePointerPosition(false /* forIme */);
- mExpandedAnimationController.expandFromStack(() -> {
- afterExpandedViewAnimation();
- mExpandedViewContainer.setVisibility(VISIBLE);
- showManageMenu(mShowingManage);
- } /* after */);
+ updatePointerPosition(false);
+ requestUpdate();
+ showManageMenu(mShowingManage);
+
PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
getState());
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
@@ -1027,6 +1029,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setTranslationY(translationY);
mExpandedViewContainer.setAlpha(1f);
}
+
removeOnLayoutChangeListener(mOrientationChangedListener);
};
final float maxDismissSize = getResources().getDimensionPixelSize(
@@ -1784,13 +1787,14 @@ public class BubbleStackView extends FrameLayout
mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
mStackAnimationController.setStackPosition(startPosition);
mExpandedAnimationController.setCollapsePoint(startPosition);
- // Set the translation x so that this bubble will animate in from the same side they
- // expand / collapse on.
- bubble.getIconView().setTranslationX(startPosition.x);
} else if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
+ // Set the view translation x so that this bubble will animate in from the same side they
+ // expand / collapse on.
+ bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x);
+
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
@@ -1864,6 +1868,14 @@ public class BubbleStackView extends FrameLayout
: GONE);
}
+ private void updateOverflowDotVisibility(boolean expanding) {
+ if (mBubbleOverflow.showDot()) {
+ mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
+ mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
+ });
+ }
+ }
+
// via BubbleData.Listener
void updateBubble(Bubble bubble) {
animateInFlyoutForBubble(bubble);
@@ -2037,6 +2049,7 @@ public class BubbleStackView extends FrameLayout
});
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
+ announceExpandForAccessibility(mExpandedBubble, mIsExpanded);
}
/**
@@ -2053,6 +2066,34 @@ public class BubbleStackView extends FrameLayout
}
}
+ private void announceExpandForAccessibility(BubbleViewProvider bubble, boolean expanded) {
+ if (bubble instanceof Bubble) {
+ String contentDescription = getBubbleContentDescription((Bubble) bubble);
+ String message = getResources().getString(
+ expanded
+ ? R.string.bubble_accessibility_announce_expand
+ : R.string.bubble_accessibility_announce_collapse, contentDescription);
+ announceForAccessibility(message);
+ }
+ }
+
+ @NonNull
+ private String getBubbleContentDescription(Bubble bubble) {
+ final String appName = bubble.getAppName();
+ final String title = bubble.getTitle() != null
+ ? bubble.getTitle()
+ : getResources().getString(R.string.notification_bubble_title);
+
+ if (appName == null || title.equals(appName)) {
+ // App bubble title equals the app name, so return only the title to avoid having
+ // content description like: `<app> from <app>`.
+ return title;
+ } else {
+ return getResources().getString(
+ R.string.bubble_content_description_single, title, appName);
+ }
+ }
+
private boolean isGestureNavEnabled() {
return mContext.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
@@ -2199,26 +2240,27 @@ public class BubbleStackView extends FrameLayout
private void showScrim(boolean show, Runnable after) {
AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animation) {
- mScrimAnimating = true;
- }
-
- @Override
public void onAnimationEnd(Animator animation) {
- mScrimAnimating = false;
+ mScrimAnimation = null;
if (after != null) {
after.run();
}
}
};
+ if (mScrimAnimation != null) {
+ // Cancel scrim animation if it animates
+ mScrimAnimation.cancel();
+ }
if (show) {
- mScrim.animate()
+ mScrimAnimation = mScrim.animate();
+ mScrimAnimation
.setInterpolator(ALPHA_IN)
- .alpha(SCRIM_ALPHA)
+ .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
.setListener(listener)
.start();
} else {
- mScrim.animate()
+ mScrimAnimation = mScrim.animate();
+ mScrimAnimation
.alpha(0f)
.setInterpolator(ALPHA_OUT)
.setListener(listener)
@@ -2245,6 +2287,7 @@ public class BubbleStackView extends FrameLayout
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
+ updateOverflowDotVisibility(true /* expanding */);
} /* after */);
int index;
if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
@@ -2376,11 +2419,15 @@ public class BubbleStackView extends FrameLayout
// since we're about to animate collapsed.
mExpandedAnimationController.notifyPreparingToCollapse();
+ updateOverflowDotVisibility(false /* expanding */);
final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack(
mStackAnimationController
.getStackPositionAlongNearestHorizontalEdge()
/* collapseTo */,
- () -> mBubbleContainer.setActiveController(mStackAnimationController));
+ () -> {
+ mBubbleContainer.setActiveController(mStackAnimationController);
+ updateOverflowVisibility();
+ });
final Runnable after = () -> {
final BubbleViewProvider previouslySelected = mExpandedBubble;
@@ -2395,7 +2442,6 @@ public class BubbleStackView extends FrameLayout
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
mExpandedBubble));
}
- updateOverflowVisibility();
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2943,7 +2989,7 @@ public class BubbleStackView extends FrameLayout
mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
- .alpha(show ? SCRIM_ALPHA : 0f)
+ .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
.withEndAction(endAction)
.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 39e3180ffe2a..bb30c5eeebcf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -155,14 +155,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Bitmap rawBadgeBitmap;
// Only populated when showing in taskbar
- BubbleBarExpandedView bubbleBarExpandedView;
+ @Nullable BubbleBarExpandedView bubbleBarExpandedView;
// These are only populated when not showing in taskbar
- BadgedImageView imageView;
- BubbleExpandedView expandedView;
+ @Nullable BadgedImageView imageView;
+ @Nullable BubbleExpandedView expandedView;
int dotColor;
Path dotPath;
- Bubble.FlyoutMessage flyoutMessage;
+ @Nullable Bubble.FlyoutMessage flyoutMessage;
Bitmap bubbleBitmap;
Bitmap badgeBitmap;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
new file mode 100644
index 000000000000..9e8a385262e4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * Observer used to identify tasks that are opening or moving to front. If a bubble activity is
+ * currently opened when this happens, we'll collapse the bubbles.
+ */
+public class BubblesTransitionObserver implements Transitions.TransitionObserver {
+
+ private BubbleController mBubbleController;
+ private BubbleData mBubbleData;
+
+ public BubblesTransitionObserver(BubbleController controller,
+ BubbleData bubbleData) {
+ mBubbleController = controller;
+ mBubbleData = bubbleData;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // We only care about opens / move to fronts when bubbles are expanded & not animating.
+ if (taskInfo == null
+ || taskInfo.taskId == INVALID_TASK_ID
+ || !TransitionUtil.isOpeningType(change.getMode())
+ || mBubbleController.isStackAnimating()
+ || !mBubbleData.isExpanded()
+ || mBubbleData.getSelectedBubble() == null) {
+ continue;
+ }
+ int expandedId = mBubbleData.getSelectedBubble().getTaskId();
+ // If the task id that's opening is the same as the expanded bubble, skip collapsing
+ // because it is our bubble that is opening.
+ if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+ mBubbleData.setExpanded(false);
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {
+
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+
+ }
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
+
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
index ed3624035757..48692d41016e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
@@ -22,6 +22,7 @@ import com.android.wm.shell.common.bubbles.DismissView
fun DismissView.setup() {
setup(DismissView.Config(
+ dismissViewResId = R.id.dismiss_view,
targetSizeResId = R.dimen.dismiss_circle_size,
iconSizeResId = R.dimen.dismiss_target_x_size,
bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 4dda0688b790..5776ad109d19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -39,4 +39,6 @@ interface IBubbles {
oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
+ oneway void showUserEducation(in int positionX, in int positionY) = 8;
+
} \ No newline at end of file
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 4d7042bbb3d2..79f306ece283 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.animation;
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.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.res.Resources;
import android.graphics.Path;
@@ -34,6 +35,8 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BadgedImageView;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -63,6 +66,12 @@ public class ExpandedAnimationController
/** Damping ratio for expand/collapse spring. */
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
+ /**
+ * Damping ratio for the overflow bubble spring; this is less bouncy so it doesn't bounce behind
+ * the top bubble when it goes to disappear.
+ */
+ private static final float DAMPING_RATIO_OVERFLOW_BOUNCY = 0.90f;
+
/** Stiffness for the expand/collapse path-following animation. */
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
@@ -274,9 +283,14 @@ public class ExpandedAnimationController
// of the screen where the bubble will be stacked.
path.lineTo(stackedX, p.y);
+ // The overflow should animate to the collapse point, so 0 offset.
+ final boolean isOverflow = bubble instanceof BadgedImageView
+ && BubbleOverflow.KEY.equals(((BadgedImageView) bubble).getKey());
+ final float offsetY = isOverflow
+ ? 0
+ : Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx;
// Then, draw a line down to the stack position.
- path.lineTo(stackedX, mCollapsePoint.y
- + Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx);
+ path.lineTo(stackedX, mCollapsePoint.y + offsetY);
}
// The lead bubble should be the bubble with the longest distance to travel when we're
@@ -362,6 +376,9 @@ public class ExpandedAnimationController
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
private void springBubbleTo(View bubble, float x, float y) {
@@ -505,8 +522,12 @@ public class ExpandedAnimationController
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ boolean isOverflow = (view instanceof BadgedImageView)
+ && BubbleOverflow.KEY.equals(((BadgedImageView) view).getKey());
return new SpringForce()
- .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
+ .setDampingRatio(isOverflow
+ ? DAMPING_RATIO_OVERFLOW_BOUNCY
+ : DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 000000000000..2a44f04f1358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+ /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+ /**
+ * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+ * portrait.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+ /**
+ * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+ * landscape.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+ /** Returns the dismiss target width for the specified [screenWidthPx]. */
+ @JvmStatic
+ fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+ screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+ screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+ else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+ }
+}
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 aad268394305..e48732801094 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -851,6 +852,15 @@ public class StackAnimationController extends
if (mLayout != null) {
Resources res = mLayout.getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ updateFlingToDismissTargetWidth();
+ }
+ }
+
+ private void updateFlingToDismissTargetWidth() {
+ if (mLayout != null && mMagnetizedStack != null) {
+ int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedStack.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
}
@@ -1022,23 +1032,8 @@ public class StackAnimationController extends
};
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ updateFlingToDismissTargetWidth();
}
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float minVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_fling_min_velocity",
- mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
- final float maxVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_stick_max_velocity",
- mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
- final float targetWidth = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_target_width_percent",
- mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
- mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
- mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
- mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
return mMagnetizedStack;
}
@@ -1053,7 +1048,7 @@ public class StackAnimationController extends
* property directly to move the first bubble and cause the stack to 'follow' to the new
* location.
*
- * This could also be achieved by simply animating the first bubble view and adding an update
+ * <p>This could also be achieved by simply animating the first bubble view and adding an update
* listener to dispatch movement to the rest of the stack. However, this would require
* duplication of logic in that update handler - it's simpler to keep all logic contained in the
* {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 8f11253290ea..e788341df5f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
@@ -36,6 +37,8 @@ import com.android.wm.shell.bubbles.BubbleViewProvider;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
* manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -111,7 +114,7 @@ public class BubbleBarLayerView extends FrameLayout
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
if (mExpandedView != null) {
- mEducationViewController.hideManageEducation(/* animated = */ false);
+ mEducationViewController.hideEducation(/* animated = */ false);
removeView(mExpandedView);
mExpandedView = null;
}
@@ -171,7 +174,9 @@ public class BubbleBarLayerView extends FrameLayout
mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
@Override
public void onTaskCreated() {
- mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ if (mEducationViewController != null && mExpandedView != null) {
+ mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ }
}
@Override
@@ -190,6 +195,10 @@ public class BubbleBarLayerView extends FrameLayout
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
+ }
+
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
@@ -210,7 +219,7 @@ public class BubbleBarLayerView extends FrameLayout
public void collapse() {
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ mEducationViewController.hideEducation(/* animated = */ true);
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
@@ -218,6 +227,21 @@ public class BubbleBarLayerView extends FrameLayout
showScrim(false);
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ mEducationViewController.showStackEducation(position, /* root = */ this, () -> {
+ // When the user education is clicked hide it and expand the selected bubble
+ mEducationViewController.hideEducation(/* animated = */ true, () -> {
+ mBubbleController.expandStackWithSelectedBubble();
+ return Unit.INSTANCE;
+ });
+ return Unit.INSTANCE;
+ });
+ }
+
/** Sets the function to call to un-bubble the given conversation. */
public void setUnBubbleConversationCallback(
@Nullable Consumer<String> unBubbleConversationCallback) {
@@ -226,8 +250,8 @@ public class BubbleBarLayerView extends FrameLayout
/** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
private void hideMenuOrCollapse() {
- if (mEducationViewController.isManageEducationVisible()) {
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
} else if (isExpanded() && mExpandedView != null) {
mExpandedView.hideMenuOrCollapse();
} else {
@@ -275,7 +299,7 @@ public class BubbleBarLayerView extends FrameLayout
*/
private void getTouchableRegion(Region outRegion) {
mTempRect.setEmpty();
- if (mIsExpanded) {
+ if (mIsExpanded || mEducationViewController.isEducationVisible()) {
getBoundsOnScreen(mTempRect);
outRegion.op(mTempRect, Region.Op.UNION);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7b39c6fd4059..ee552ae204b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -15,23 +15,34 @@
*/
package com.android.wm.shell.bubbles.bar
+import android.annotation.LayoutRes
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Log
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.core.view.doOnLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import com.android.wm.shell.bubbles.BubbleEducationController
import com.android.wm.shell.bubbles.BubbleViewProvider
import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
import com.android.wm.shell.common.bubbles.BubblePopupView
+import kotlin.math.roundToInt
/** Manages bubble education presentation and animation */
class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
interface Listener {
- fun onManageEducationVisibilityChanged(isVisible: Boolean)
+ fun onEducationVisibilityChanged(isVisible: Boolean)
}
private var rootView: ViewGroup? = null
@@ -45,61 +56,112 @@ class BubbleEducationViewController(private val context: Context, private val li
)
}
+ private val scrimView by lazy {
+ View(context).apply {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setOnClickListener { hideEducation(animated = true) }
+ }
+ }
+
private val controller by lazy { BubbleEducationController(context) }
/** Whether the education view is visible or being animated */
- val isManageEducationVisible: Boolean
+ val isEducationVisible: Boolean
get() = educationView != null && rootView != null
/**
- * Show manage bubble education if hasn't been shown before
+ * Hide the current education view if visible
*
- * @param bubble the bubble used for the manage education check
- * @param root the view to show manage education in
+ * @param animated whether should hide with animation
*/
- fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
- if (!controller.shouldShowManageEducation(bubble)) return
- showManageEducation(root)
+ @JvmOverloads
+ fun hideEducation(animated: Boolean, endActions: () -> Unit = {}) {
+ log { "hideEducation animated: $animated" }
+
+ if (animated) {
+ animateTransition(show = false) {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
+ } else {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
}
/**
- * Hide the manage education view if visible
+ * Show bubble bar stack user education.
*
- * @param animated whether should hide with animation
+ * @param position the reference position for the user education in Screen coordinates.
+ * @param root the view to show user education in.
+ * @param educationClickHandler the on click handler for the user education view
*/
- fun hideManageEducation(animated: Boolean) {
- rootView?.let {
- fun cleanUp() {
- it.removeView(educationView)
- rootView = null
- listener.onManageEducationVisibilityChanged(isVisible = false)
- }
+ fun showStackEducation(position: Point, root: ViewGroup, educationClickHandler: () -> Unit) {
+ hideEducation(animated = false)
+ log { "showStackEducation at: $position" }
- if (animated) {
- animateTransition(show = false, ::cleanUp)
- } else {
- cleanUp()
+ educationView =
+ createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+ setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
+ setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+ updateEducationPosition(view = this, position, root)
+ val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
+ doOnLayout {
+ it.pivotX = it.width - arrowToEdgeOffset
+ it.pivotY = it.height.toFloat()
+ }
+ setOnClickListener { educationClickHandler() }
}
+
+ rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
+ animateTransition(show = true) {
+ controller.hasSeenStackEducation = true
+ listener.onEducationVisibilityChanged(isVisible = true)
}
}
/**
+ * Show manage bubble education if hasn't been shown before
+ *
+ * @param bubble the bubble used for the manage education check
+ * @param root the view to show manage education in
+ */
+ fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+ log { "maybeShowManageEducation bubble: $bubble" }
+ if (!controller.shouldShowManageEducation(bubble)) return
+ showManageEducation(root)
+ }
+
+ /**
* Show manage education with animation
*
* @param root the view to show manage education in
*/
private fun showManageEducation(root: ViewGroup) {
- hideManageEducation(animated = false)
- if (educationView == null) {
- val eduView = createEducationView(root)
- educationView = eduView
- animator = createAnimation(eduView)
- }
- root.addView(educationView)
+ hideEducation(animated = false)
+ log { "showManageEducation" }
+
+ educationView =
+ createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+ pivotY = 0f
+ doOnLayout { it.pivotX = it.width / 2f }
+ setOnClickListener { hideEducation(animated = true) }
+ }
+
rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
animateTransition(show = true) {
controller.hasSeenManageEducation = true
- listener.onManageEducationVisibilityChanged(isVisible = true)
+ listener.onEducationVisibilityChanged(isVisible = true)
}
}
@@ -110,39 +172,75 @@ class BubbleEducationViewController(private val context: Context, private val li
* @param endActions a closure to be called when the animation completes
*/
private fun animateTransition(show: Boolean, endActions: () -> Unit) {
- animator?.let { animator ->
- animator
- .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
- .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
- .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
- .withEndActions(endActions)
- .start()
- } ?: endActions()
+ animator
+ ?.spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+ ?.spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.withEndActions(endActions)
+ ?.start()
+ ?: endActions()
+ }
+
+ /** Remove education view from the root and clean up all relative properties */
+ private fun cleanUp() {
+ log { "cleanUp" }
+ rootView?.removeView(educationView)
+ rootView?.removeView(scrimView)
+ educationView = null
+ rootView = null
+ animator = null
}
- private fun createEducationView(root: ViewGroup): BubblePopupView {
- val view =
- LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
- as BubblePopupView
-
- return view.apply {
- setup()
- alpha = 0f
- pivotY = 0f
- scaleX = EDU_SCALE_HIDDEN
- scaleY = EDU_SCALE_HIDDEN
- doOnLayout { it.pivotX = it.width / 2f }
- setOnClickListener { hideManageEducation(animated = true) }
+ /**
+ * Create education view by inflating layout provided.
+ *
+ * @param layout layout resource id to inflate. The root view should be [BubblePopupView]
+ * @param root view group to use as root for inflation, is not attached to root
+ */
+ private fun createEducationView(@LayoutRes layout: Int, root: ViewGroup): BubblePopupView {
+ val view = LayoutInflater.from(context).inflate(layout, root, false) as BubblePopupView
+ view.setup()
+ view.alpha = 0f
+ view.scaleX = EDU_SCALE_HIDDEN
+ view.scaleY = EDU_SCALE_HIDDEN
+ return view
+ }
+
+ /** Create animator for the user education transitions */
+ private fun createAnimator(): PhysicsAnimator<BubblePopupView>? {
+ return educationView?.let {
+ PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
}
}
- private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
- val animator = PhysicsAnimator.getInstance(view)
- animator.setDefaultSpringConfig(springConfig)
- return animator
+ /**
+ * Update user education view position relative to the reference position
+ *
+ * @param view the user education view to layout
+ * @param position the reference position in Screen coordinates
+ * @param root the root view to use for the layout
+ */
+ private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
+ val rootBounds = Rect()
+ // Get root bounds on screen as position is in screen coordinates
+ root.getBoundsOnScreen(rootBounds)
+ // Get the offset to the arrow from the edge of the education view
+ val arrowToEdgeOffset =
+ view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
+ ?: 0
+ // Calculate education view margins
+ val params = view.layoutParams as FrameLayout.LayoutParams
+ params.bottomMargin = rootBounds.bottom - position.y
+ params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+ view.layoutParams = params
+ }
+
+ private fun log(msg: () -> String) {
+ if (DEBUG_USER_EDUCATION) Log.d(TAG, msg())
}
companion object {
+ private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationViewController" else TAG_BUBBLES
private const val EDU_SCALE_HIDDEN = 0.5f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
index 85aaa8ef585c..4206d9320b7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -29,4 +29,7 @@ interface BubbleProperties {
* When this is `false`, bubbles will be floating.
*/
val isBubbleBarEnabled: Boolean
+
+ /** Refreshes the current value of [isBubbleBarEnabled]. */
+ fun refresh()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index 9d8b9a6f3260..e1dea3babbc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -22,6 +22,13 @@ import android.os.SystemProperties
object ProdBubbleProperties : BubbleProperties {
// TODO(b/256873975) Should use proper flag when available to shell/launcher
- override val isBubbleBarEnabled =
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ private var _isBubbleBarEnabled =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+
+ override val isBubbleBarEnabled
+ get() = _isBubbleBarEnabled
+
+ override fun refresh() {
+ _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 72702e7c2b88..b828aac39040 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.common;
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import android.view.IDisplayChangeWindowCallback;
import android.view.IDisplayChangeWindowController;
@@ -40,6 +41,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class DisplayChangeController {
private static final String TAG = DisplayChangeController.class.getSimpleName();
+ private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange";
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
@@ -81,9 +83,15 @@ public class DisplayChangeController {
/** Query all listeners for changes that should happen on display change. */
void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginSection("dispatchOnDisplayChange");
+ }
for (OnDisplayChangingListener c : mDisplayChangeListener) {
c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endSection();
+ }
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -94,6 +102,10 @@ public class DisplayChangeController {
callback.continueDisplayChange(t);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to continue handling display change", e);
+ } finally {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
}
}
@@ -103,6 +115,9 @@ public class DisplayChangeController {
@Override
public void onDisplayChange(int displayId, int fromRotation, int toRotation,
DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
mMainExecutor.execute(() -> DisplayChangeController.this
.onDisplayChange(displayId, fromRotation, toRotation,
newDisplayAreaInfo, callback));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
index 7af038999797..6519eee18e69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -1 +1,2 @@
madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 5e42782431fd..e9344ffcce0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -203,7 +203,7 @@ public class SystemWindows {
+ "SystemWindow:" + view);
return null;
}
- return root.getFocusGrantToken();
+ return root.getInputTransferToken();
}
private class PerDisplay {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 931cf0cee28c..c6c9b3562308 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -94,6 +94,10 @@ public class TvWindowMenuActionButton extends RelativeLayout {
mCurrentIcon = icon;
// Remove old image while waiting for the new one to load.
mIconImageView.setImageDrawable(null);
+ if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // Disallow loading icon from content URI
+ return;
+ }
icon.loadDrawableAsync(mContext, d -> {
// The image hasn't been set any other way and the drawable belongs to the most
// recently set Icon.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index 81423473171d..fc627a8dcb36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -35,6 +35,7 @@ public class BubbleBarUpdate implements Parcelable {
public boolean expandedChanged;
public boolean expanded;
+ public boolean shouldShowEducation;
@Nullable
public String selectedBubbleKey;
@Nullable
@@ -61,6 +62,7 @@ public class BubbleBarUpdate implements Parcelable {
public BubbleBarUpdate(Parcel parcel) {
expandedChanged = parcel.readBoolean();
expanded = parcel.readBoolean();
+ shouldShowEducation = parcel.readBoolean();
selectedBubbleKey = parcel.readString();
addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
BubbleInfo.class);
@@ -95,6 +97,7 @@ public class BubbleBarUpdate implements Parcelable {
return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ " expanded=" + expanded
+ " selectedBubbleKey=" + selectedBubbleKey
+ + " shouldShowEducation=" + shouldShowEducation
+ " addedBubble=" + addedBubble
+ " updatedBubble=" + updatedBubble
+ " suppressedBubbleKey=" + suppressedBubbleKey
@@ -114,6 +117,7 @@ public class BubbleBarUpdate implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeBoolean(expandedChanged);
parcel.writeBoolean(expanded);
+ parcel.writeBoolean(shouldShowEducation);
parcel.writeString(selectedBubbleKey);
parcel.writeParcelable(addedBubble, flags);
parcel.writeParcelable(updatedBubble, flags);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
index 6a46d23ad2a1..0329b8df7544 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.bubble
+package com.android.wm.shell.common.bubbles;
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+/**
+ * Constants shared between bubbles in shell & things we have to do for bubbles in launcher.
+ */
+public class BubbleConstants {
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
- OpenActivityFromBubbleTest(flicker)
+ /** The alpha for the scrim shown when bubbles are expanded. */
+ public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
index 1fd22d0a3505..887af17c9653 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -31,7 +31,7 @@ import kotlin.math.sin
import kotlin.properties.Delegates
/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
-class BubblePopupDrawable(private val config: Config) : Drawable() {
+class BubblePopupDrawable(val config: Config) : Drawable() {
/** The direction of the arrow in the popup drawable */
enum class ArrowDirection {
UP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
index f8a4946bb5c5..444fbf7884be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -29,7 +29,8 @@ constructor(
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
- private var popupDrawable: BubblePopupDrawable? = null
+ var popupDrawable: BubblePopupDrawable? = null
+ private set
/**
* Sets up the popup drawable with the config provided. Required to remove dependency on local
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
index d275a0be8e93..2eb55e19a960 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -49,6 +49,8 @@ class DismissView(context: Context) : FrameLayout(context) {
* @see [setup] method
*/
data class Config(
+ /** The resource id to set on the dismiss target circle view */
+ val dismissViewResId: Int,
/** dimen resource id of the dismiss target circle view size */
@DimenRes val targetSizeResId: Int,
/** dimen resource id of the icon size in the dismiss target */
@@ -121,6 +123,7 @@ class DismissView(context: Context) : FrameLayout(context) {
setBackgroundDrawable(gradientDrawable)
// Setup DismissCircleView
+ circle.id = config.dismissViewResId
circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
circle.layoutParams = LayoutParams(targetSize, targetSize,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
index cc37bd3a4589..d45e1265daac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -112,7 +112,7 @@ abstract class RelativeTouchListener : View.OnTouchListener {
viewPositionOnTouchDown.set(v.translationX, v.translationY)
performedLongClick = false
- v.handler.postDelayed({
+ v.handler?.postDelayed({
if (v.isLongClickable) {
performedLongClick = v.performLongClick()
}
@@ -122,7 +122,7 @@ abstract class RelativeTouchListener : View.OnTouchListener {
MotionEvent.ACTION_MOVE -> {
if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
movedEnough = true
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
if (movedEnough) {
@@ -138,7 +138,7 @@ abstract class RelativeTouchListener : View.OnTouchListener {
} else if (!performedLongClick) {
v.performClick()
} else {
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
}
velocityTracker.clear()
@@ -146,7 +146,7 @@ abstract class RelativeTouchListener : View.OnTouchListener {
}
MotionEvent.ACTION_CANCEL -> {
- v.handler.removeCallbacksAndMessages(null)
+ v.handler?.removeCallbacksAndMessages(null)
velocityTracker.clear()
movedEnough = false
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 3b32b6c7b083..d520ff791e07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -126,12 +126,22 @@ public class PipBoundsState {
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
+ // the size of the current bounds relative to the max size spec
+ private float mBoundsScale;
+
public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
@NonNull PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
mSizeSpecSource = sizeSpecSource;
mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ // Update the relative proportion of the bounds compared to max possible size. Max size
+ // spec takes the aspect ratio of the bounds into account, so both width and height
+ // scale by the same factor.
+ addPipExclusionBoundsChangeCallback((bounds) -> {
+ mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+ });
}
/** Reloads the resources. */
@@ -160,6 +170,15 @@ public class PipBoundsState {
return new Rect(mBounds);
}
+ /**
+ * Get the scale of the current bounds relative to the maximum size possible.
+ *
+ * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}.
+ */
+ public float getBoundsScale() {
+ return mBoundsScale;
+ }
+
/** Returns the current movement bounds. */
@NonNull
public Rect getMovementBounds() {
@@ -622,6 +641,9 @@ public class PipBoundsState {
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip);
pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+ pw.println(innerPrefix + "mBoundsScale" + mBoundsScale);
if (mPipReentryState == null) {
pw.println(innerPrefix + "mPipReentryState=null");
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 1901e0bbe700..f9a286ec804f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -20,6 +20,15 @@ import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -28,6 +37,8 @@ import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.DisplayInfo;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
/**
@@ -195,6 +206,21 @@ public class DividerSnapAlgorithm {
}
}
+ /**
+ * Gets the SnapTarget corresponding to the given {@link SnapPosition}, or null if no such
+ * SnapTarget exists.
+ */
+ @Nullable
+ public SnapTarget findSnapTarget(@SnapPosition int snapPosition) {
+ for (SnapTarget t : mTargets) {
+ if (t.snapPosition == snapPosition) {
+ return t;
+ }
+ }
+
+ return null;
+ }
+
public float calculateDismissingFraction(int position) {
if (position < mFirstSplitTarget.position) {
return 1f - (float) (position - getStartInset())
@@ -263,7 +289,7 @@ public class DividerSnapAlgorithm {
private SnapTarget snap(int position, boolean hardDismiss) {
if (shouldApplyFreeSnapMode(position)) {
- return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+ return new SnapTarget(position, position, SNAP_TO_NONE);
}
int minIndex = -1;
float minDistance = Float.MAX_VALUE;
@@ -291,8 +317,7 @@ public class DividerSnapAlgorithm {
if (dockedSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
- mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
- 0.35f));
+ mTargets.add(new SnapTarget(startPos, startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
@@ -307,15 +332,15 @@ public class DividerSnapAlgorithm {
addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
- mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+ mTargets.add(new SnapTarget(dividerMax, dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
}
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
- maybeAddTarget(topPosition, topPosition - getStartInset());
+ maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
addMiddleTarget(isHorizontalDivision);
maybeAddTarget(bottomPosition,
- dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
}
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -349,16 +374,16 @@ public class DividerSnapAlgorithm {
* Adds a target at {@param position} but only if the area with size of {@param smallerSize}
* meets the minimal size requirement.
*/
- private void maybeAddTarget(int position, int smallerSize) {
+ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) {
if (smallerSize >= mMinimalSizeResizableTask) {
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, snapPosition));
}
}
private void addMiddleTarget(boolean isHorizontalDivision) {
int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_50_50));
}
private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
@@ -372,7 +397,7 @@ public class DividerSnapAlgorithm {
position = mDisplayWidth - position - mInsets.right - mDividerSize;
}
}
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_MINIMIZE));
}
public SnapTarget getMiddleTarget() {
@@ -412,6 +437,13 @@ public class DividerSnapAlgorithm {
}
/**
+ * Finds the {@link SnapPosition} nearest to the given position.
+ */
+ public int calculateNearestSnapPosition(int currentPosition) {
+ return snap(currentPosition, /* hardDismiss */ true).snapPosition;
+ }
+
+ /**
* Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
* if {@param increment} is negative and moves right otherwise.
*/
@@ -435,14 +467,6 @@ public class DividerSnapAlgorithm {
* Represents a snap target for the divider.
*/
public static class SnapTarget {
- public static final int FLAG_NONE = 0;
-
- /** If the divider reaches this value, the left/top task should be dismissed. */
- public static final int FLAG_DISMISS_START = 1;
-
- /** If the divider reaches this value, the right/bottom task should be dismissed */
- public static final int FLAG_DISMISS_END = 2;
-
/** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
public final int position;
@@ -452,7 +476,10 @@ public class DividerSnapAlgorithm {
*/
public final int taskPosition;
- public final int flag;
+ /**
+ * An int describing the placement of the divider in this snap target.
+ */
+ public final @SnapPosition int snapPosition;
public boolean isMiddleTarget;
@@ -462,14 +489,15 @@ public class DividerSnapAlgorithm {
*/
private final float distanceMultiplier;
- public SnapTarget(int position, int taskPosition, int flag) {
- this(position, taskPosition, flag, 1f);
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition) {
+ this(position, taskPosition, snapPosition, 1f);
}
- public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapPosition,
+ float distanceMultiplier) {
this.position = position;
this.taskPosition = taskPosition;
- this.flag = flag;
+ this.snapPosition = snapPosition;
this.distanceMultiplier = distanceMultiplier;
}
}
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 755dba0c895f..63cdb4f151ff 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
@@ -26,10 +26,10 @@ import static android.view.WindowManager.DOCKED_TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -67,6 +67,8 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import java.io.PrintWriter;
@@ -116,7 +118,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
private WindowContainerToken mWinToken1;
private WindowContainerToken mWinToken2;
- private int mDividePosition;
+ private int mDividerPosition;
private boolean mInitialized = false;
private boolean mFreezeDividerWindow = false;
private int mOrientation;
@@ -268,7 +270,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
int getDividePosition() {
- return mDividePosition;
+ return mDividerPosition;
+ }
+
+ /**
+ * Finds the {@link SnapPosition} nearest to the current divider position.
+ */
+ public int calculateCurrentSnapPosition() {
+ return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
}
/**
@@ -345,16 +354,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
private void initDividerPosition(Rect oldBounds) {
- final float snapRatio = (float) mDividePosition
+ final float snapRatio = (float) mDividerPosition
/ (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
// Estimate position by previous ratio.
final float length =
(float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
final int estimatePosition = (int) (length * snapRatio);
// Init divider position by estimated position using current bounds snap algorithm.
- mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
+ mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
estimatePosition).position;
- updateBounds(mDividePosition);
+ updateBounds(mDividerPosition);
}
private void updateBounds(int position) {
@@ -468,27 +477,29 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
void setDividePosition(int position, boolean applyLayoutChange) {
- mDividePosition = position;
- updateBounds(mDividePosition);
+ mDividerPosition = position;
+ updateBounds(mDividerPosition);
if (applyLayoutChange) {
mSplitLayoutHandler.onLayoutSizeChanged(this);
}
}
/** Updates divide position and split bounds base on the ratio within root bounds. */
- public void setDivideRatio(float ratio) {
- final int position = isLandscape()
- ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
- : mRootBounds.top + (int) (mRootBounds.height() * ratio);
- final DividerSnapAlgorithm.SnapTarget snapTarget =
- mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
+ public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
+ final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
+ snapPosition);
+
+ if (snapTarget == null) {
+ throw new IllegalArgumentException("No SnapTarget for position " + snapPosition);
+ }
+
setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
/** Resets divider position. */
public void resetDividerPosition() {
- mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
- updateBounds(mDividePosition);
+ mDividerPosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividerPosition);
mWinToken1 = null;
mWinToken2 = null;
mWinBounds1.setEmpty();
@@ -511,13 +522,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* target indicates dismissing split.
*/
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
- switch (snapTarget.flag) {
- case FLAG_DISMISS_START:
+ switch (snapTarget.snapPosition) {
+ case SNAP_TO_START_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
- case FLAG_DISMISS_END:
+ case SNAP_TO_END_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
@@ -669,8 +680,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public void onAnimationEnd(Animator animation) {
- mDividePosition = dividerPos;
- updateBounds(mDividePosition);
+ mDividerPosition = dividerPos;
+ updateBounds(mDividerPosition);
finishCallback.accept(insets);
InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index be1b9b1227de..e73430056c89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -26,6 +26,16 @@ import android.annotation.IntDef;
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
+ /**
+ * Duration used for every split fade-in or fade-out.
+ */
+ public static final int FADE_DURATION = 133;
+
+ ///////////////
+ // IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants:
+ // These int values must not be changed -- they are persisted to user-defined app pairs, and
+ // will break things if changed.
+ //
/**
* Split position isn't specified normally meaning to use what ever it is currently set to.
@@ -44,11 +54,6 @@ public class SplitScreenConstants {
*/
public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
- /**
- * Duration used for every split fade-in or fade-out.
- */
- public static final int FADE_DURATION = 133;
-
@IntDef(prefix = {"SPLIT_POSITION_"}, value = {
SPLIT_POSITION_UNDEFINED,
SPLIT_POSITION_TOP_OR_LEFT,
@@ -57,6 +62,61 @@ public class SplitScreenConstants {
public @interface SplitPosition {
}
+ /** A snap target in the first half of the screen, where the split is roughly 30-70. */
+ public static final int SNAP_TO_30_70 = 0;
+
+ /** The 50-50 snap target */
+ public static final int SNAP_TO_50_50 = 1;
+
+ /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
+ public static final int SNAP_TO_70_30 = 2;
+
+ /**
+ * These snap targets are used for split pairs in a stable, non-transient state. They may be
+ * persisted in Launcher when the user saves an app pair. They are a subset of
+ * {@link SnapPosition}.
+ */
+ @IntDef(prefix = { "SNAP_TO_" }, value = {
+ SNAP_TO_30_70,
+ SNAP_TO_50_50,
+ SNAP_TO_70_30
+ })
+ public @interface PersistentSnapPosition {}
+
+ /**
+ * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
+ */
+ public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
+ return snapPosition == SNAP_TO_30_70
+ || snapPosition == SNAP_TO_50_50
+ || snapPosition == SNAP_TO_70_30;
+ }
+
+ /** The divider doesn't snap to any target and is freely placeable. */
+ public static final int SNAP_TO_NONE = 10;
+
+ /** If the divider reaches this value, the left/top task should be dismissed. */
+ public static final int SNAP_TO_START_AND_DISMISS = 11;
+
+ /** If the divider reaches this value, the right/bottom task should be dismissed. */
+ public static final int SNAP_TO_END_AND_DISMISS = 12;
+
+ /** A snap target positioned near the screen edge for a minimized task */
+ public static final int SNAP_TO_MINIMIZE = 13;
+
+ @IntDef(prefix = { "SNAP_TO_" }, value = {
+ SNAP_TO_30_70,
+ SNAP_TO_50_50,
+ SNAP_TO_70_30,
+ SNAP_TO_NONE,
+ SNAP_TO_START_AND_DISMISS,
+ SNAP_TO_END_AND_DISMISS,
+ SNAP_TO_MINIMIZE
+ })
+ public @interface SnapPosition {}
+
+ ///////////////
+
public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 9facbd542e6c..b52a118c7f1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -49,11 +49,11 @@ import dagger.Provides;
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines Shell dependencies for the TV SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = {TvPipModule.class})
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 45869e177c6d..ea7b2e92fefb 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
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.SystemPerformanceHinter;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -38,6 +39,7 @@ import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DevicePostureController;
@@ -71,7 +73,6 @@ import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.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;
@@ -85,6 +86,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.performance.PerfHintController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -118,9 +120,9 @@ import java.util.Optional;
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines *common* dependencies across various SystemUI implementations,
+ * <p>This module only defines *common* dependencies across various SystemUI implementations,
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
@@ -296,6 +298,17 @@ public abstract class WMShellBaseModule {
return new LaunchAdjacentController(syncQueue);
}
+ @WMSingleton
+ @Provides
+ static SystemPerformanceHinter provideSystemPerformanceHinter(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
+ final PerfHintController perfHintController =
+ new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
+ return perfHintController.getHinter();
+ }
+
//
// Back animation
//
@@ -322,16 +335,26 @@ public abstract class WMShellBaseModule {
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
- BackAnimationBackground backAnimationBackground
- ) {
+ BackAnimationBackground backAnimationBackground,
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context, backAnimationBackground));
+ return shellBackAnimationRegistry.map(
+ (animations) ->
+ new BackAnimationController(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ context,
+ backAnimationBackground,
+ animations));
}
return Optional.empty();
}
+ @BindsOptionalOf
+ abstract ShellBackAnimationRegistry optionalBackAnimationRegistry();
+
//
// PiP (optional feature)
//
@@ -582,6 +605,7 @@ public abstract class WMShellBaseModule {
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
@@ -589,14 +613,13 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- ShellCommandHandler shellCommandHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
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, shellCommandHandler,
+ return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
+ pool, displayController, mainExecutor, mainHandler, animExecutor,
rootTaskDisplayAreaOrganizer);
}
@@ -635,15 +658,15 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer(
- @ShellMainThread ShellExecutor mainExecutor, Context context) {
- return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
+ @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) {
+ return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit);
}
@WMSingleton
@Provides
static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
- @ShellMainThread ShellExecutor mainExecutor) {
- return new RootDisplayAreaOrganizer(mainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) {
+ return new RootDisplayAreaOrganizer(mainExecutor, shellInit);
}
@WMSingleton
@@ -789,30 +812,10 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController) {
- if (DesktopModeStatus.isProto2Enabled()) {
- return desktopTasksController.map(DesktopTasksController::asDesktopMode);
- }
- return desktopModeController.map(DesktopModeController::asDesktopMode);
+ return desktopTasksController.map(DesktopTasksController::asDesktopMode);
}
- @BindsOptionalOf
- @DynamicOverride
- abstract DesktopModeController optionalDesktopModeController();
-
- @WMSingleton
- @Provides
- 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
@@ -825,7 +828,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
return desktopTasksController.map(Lazy::get);
}
return Optional.empty();
@@ -842,7 +845,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
return desktopModeTaskRepository.map(Lazy::get);
}
return Optional.empty();
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 065e7b09a05d..a533ca5fa8fb 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
@@ -32,9 +32,11 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
@@ -52,8 +54,8 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
-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;
@@ -100,17 +102,19 @@ import java.util.List;
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link WMComponent}).
*
- * This module only defines Shell dependencies for handheld SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(includes = {
- WMShellBaseModule.class,
- PipModule.class
-})
+@Module(
+ includes = {
+ WMShellBaseModule.class,
+ PipModule.class,
+ ShellBackAnimationModule.class,
+ })
public abstract class WMShellModule {
//
@@ -132,11 +136,18 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static BubbleEducationController provideBubbleEducationProvider(Context context) {
+ return new BubbleEducationController(context);
+ }
+
+ @WMSingleton
+ @Provides
static BubbleData provideBubbleData(Context context,
BubbleLogger logger,
BubblePositioner positioner,
+ BubbleEducationController educationController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new BubbleData(context, logger, positioner, mainExecutor);
+ return new BubbleData(context, logger, positioner, educationController, mainExecutor);
}
// Note: Handler needed for LauncherApps.register
@@ -164,6 +175,7 @@ public abstract class WMShellModule {
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
@@ -173,7 +185,8 @@ public abstract class WMShellModule {
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
+ taskViewTransitions, transitions, syncQueue, wmService,
+ ProdBubbleProperties.INSTANCE);
}
//
@@ -187,26 +200,32 @@ public abstract class WMShellModule {
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController) {
- if (DesktopModeStatus.isAnyEnabled()) {
+ Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
shellInit,
+ shellCommandHandler,
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
- desktopModeController,
- desktopTasksController);
+ desktopTasksController,
+ recentsTransitionHandler,
+ rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
context,
@@ -346,14 +365,14 @@ public abstract class WMShellModule {
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
KeyguardTransitionHandler keyguardTransitionHandler,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
- keyguardTransitionHandler, desktopModeController, desktopTasksController,
- unfoldHandler);
+ keyguardTransitionHandler, desktopTasksController,
+ unfoldHandler, activityEmbeddingController);
}
@WMSingleton
@@ -464,24 +483,6 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- @ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
- return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
- mainExecutor);
- }
-
- @WMSingleton
- @Provides
- @DynamicOverride
static DesktopTasksController provideDesktopTasksController(
Context context,
ShellInit shellInit,
@@ -497,13 +498,14 @@ public abstract class WMShellModule {
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
+ RecentsTransitionHandler recentsTransitionHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
- launchAdjacentController, mainExecutor);
+ launchAdjacentController, recentsTransitionHandler, mainExecutor);
}
@WMSingleton
@@ -546,8 +548,7 @@ public abstract class WMShellModule {
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- DefaultMixedHandler defaultMixedHandler,
- Optional<DesktopModeController> desktopModeController) {
+ DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
new file mode 100644
index 000000000000..b34c6b213df4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.back;
+
+import com.android.wm.shell.back.CrossActivityAnimation;
+import com.android.wm.shell.back.CrossTaskBackAnimation;
+import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.ShellBackAnimation;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/** Default animation definitions for predictive back. */
+@Module
+public interface ShellBackAnimationModule {
+ /** Default animation registry */
+ @Provides
+ static ShellBackAnimationRegistry provideBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity,
+ @ShellBackAnimation.CrossTask ShellBackAnimation crossTask,
+ @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) {
+ return new ShellBackAnimationRegistry(
+ crossActivity,
+ crossTask,
+ customizeActivity,
+ /* defaultBackToHomeAnimation= */ null);
+ }
+
+ /** Default cross activity back animation */
+ @Binds
+ @ShellBackAnimation.CrossActivity
+ ShellBackAnimation bindCrossActivityShellBackAnimation(
+ CrossActivityAnimation crossActivityAnimation);
+
+ /** Default cross task back animation */
+ @Binds
+ @ShellBackAnimation.CrossTask
+ ShellBackAnimation provideCrossTaskShellBackAnimation(
+ CrossTaskBackAnimation crossTaskBackAnimation);
+
+ /** Default customized activity back animation */
+ @Binds
+ @ShellBackAnimation.CustomizeActivity
+ ShellBackAnimation provideCustomizeActivityShellBackAnimation(
+ CustomizeActivityAnimation customizeActivityAnimation);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 000000000000..74a29ddad073
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index af97cf68915f..1898ea737729 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,28 @@
package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
+import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
-import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
* the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@ public abstract class Pip2Module {
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ Optional<PipController> pipController) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
pipBoundsAlgorithm);
}
+
+ @WMSingleton
+ @Provides
+ static Optional<PipController> providePipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellController, displayController, displayInsetsController,
+ pipDisplayLayoutState));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 570f0a3db35a..f2631eff890d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipTransition;
import dagger.Module;
import dagger.Provides;
@@ -36,7 +37,7 @@ public abstract class PipModule {
@Provides
static PipTransitionController providePipTransitionController(
com.android.wm.shell.pip.PipTransition legacyPipTransition,
- com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ PipTransition newPipTransition) {
if (PipUtils.isPip2ExperimentEnabled()) {
return newPipTransition;
} else {
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
deleted file mode 100644
index 5b24d7a60c4e..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ /dev/null
@@ -1,557 +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.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_NONE;
-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;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.graphics.Region;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-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;
-import java.util.function.Consumer;
-
-/**
- * Handles windowing changes when desktop mode system setting changes
- */
-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 DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
- private final SettingsObserver mSettingsObserver;
-
- public DesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- DesktopModeTaskRepository desktopModeTaskRepository,
- @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);
- 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
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- /**
- * Get connection interface between sysui and shell
- */
- public DesktopMode asDesktopMode() {
- 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 addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
- }
-
- /**
- * Adds a listener to track changes to corners of desktop mode tasks.
- * @param listener the listener to add.
- * @param callbackExecutor the executor to call the listener on.
- */
- public void addTaskCornerListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.setTaskCornerListener(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 so tasks inherit it
- clearFreeformForStandardTasks(runningTasks, wct);
- if (active) {
- moveHomeBehindVisibleTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
- } else {
- clearBoundsForStandardTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
- } else {
- mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
- }
- }
-
- 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.v(WM_SHELL_DESKTOP_MODE,
- "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
- displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
- windowingMode);
-
- wct.setWindowingMode(displayAreaInfo.token, windowingMode);
- }
-
- /**
- * Show apps on desktop
- */
- void showDesktopApps(int displayId) {
- // Bring apps to front, ignoring their visibility status to always ensure they are on top.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- bringDesktopAppsToFront(displayId, wct);
-
- if (!wct.isEmpty()) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/268662477): add animation for the transition
- mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
- }
-
- /** Get number of tasks that are marked as visible */
- int getVisibleTaskCount(int displayId) {
- return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
- }
-
- private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
- final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-
- final List<RunningTaskInfo> taskInfos = new ArrayList<>();
- for (Integer taskId : activeTasks) {
- RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
- if (taskInfo != null) {
- taskInfos.add(taskInfo);
- }
- }
-
- if (taskInfos.isEmpty()) {
- return;
- }
-
- moveHomeTaskToFront(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);
- }
- }
-
- private void moveHomeTaskToFront(WindowContainerTransaction wct) {
- for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
- if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
- wct.reorder(task.token, true /* onTop */);
- return;
- }
- }
- }
-
- /**
- * Update corner rects stored for a specific task
- * @param taskId task to update
- * @param taskCorners task's new corner handles
- */
- public void onTaskCornersChanged(int taskId, Region taskCorners) {
- mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
- }
-
- /**
- * Remove corners saved for a task. Likely used due to task closure.
- * @param taskId task to remove
- */
- public void removeCornersForTask(int taskId) {
- mDesktopModeTaskRepository.removeTaskCorners(taskId);
- }
-
- /**
- * Moves a specifc task to the front.
- * @param taskInfo the task to show in front.
- */
- public void moveTaskToFront(RunningTaskInfo taskInfo) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(taskInfo.token, true /* onTop */);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
-
- /**
- * Turn desktop mode on or off
- * @param active the desired state for desktop mode setting
- */
- public void setDesktopModeActive(boolean active) {
- int value = active ? 1 : 0;
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
- }
-
- /**
- * Returns the windowing mode of the display area with the specified displayId.
- * @param displayId
- * @return
- */
- public int getDisplayAreaWindowingMode(int displayId) {
- return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
- .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) {
- RunningTaskInfo triggerTask = request.getTriggerTask();
- // 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 (triggerTask == null || triggerTask.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 = new WindowContainerTransaction();
- bringDesktopAppsToFront(triggerTask.displayId, wct);
- wct.reorder(triggerTask.token, true /* onTop */);
-
- return wct;
- }
-
- /**
- * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
- * This is intended to be used when desktop mode is part of another animation but isn't, itself,
- * animating.
- */
- public void syncSurfaceState(@NonNull TransitionInfo info,
- SurfaceControl.Transaction finishTransaction) {
- // Add rounded corners to freeform windows
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.dialogCornerRadius});
- final int cornerRadius = ta.getDimensionPixelSize(0, 0);
- ta.recycle();
- for (TransitionInfo.Change change: info.getChanges()) {
- if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
- }
- }
- }
-
- /**
- * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
- */
- private final class SettingsObserver extends ContentObserver {
-
- private final Uri mDesktopModeSetting = Settings.System.getUriFor(
- Settings.System.DESKTOP_MODE);
-
- private final Context mContext;
-
- SettingsObserver(Context context, Handler handler) {
- super(handler);
- mContext = context;
- }
-
- public void observe() {
- // TODO(b/242867463): listen for setting change for all users
- mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
- false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
- }
-
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri) {
- if (mDesktopModeSetting.equals(uri)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
- desktopModeSettingChanged();
- }
- }
-
- private void desktopModeSettingChanged() {
- boolean enabled = DesktopModeStatus.isActive(mContext);
- updateDesktopModeActive(enabled);
- }
- }
-
- /**
- * The interface for calls from outside the shell, within the host process.
- */
- @ExternalThread
- private final class DesktopModeImpl implements DesktopMode {
-
- @Override
- public void addVisibleTasksListener(
- DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
- });
- }
-
- @Override
- public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
- });
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub
- implements ExternalInterfaceBinder {
-
- private DesktopModeController mController;
-
- IDesktopModeImpl(DesktopModeController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- @Override
- public void invalidate() {
- mController = null;
- }
-
- @Override
- public void showDesktopApps(int displayId) {
- executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
- controller -> controller.showDesktopApps(displayId));
- }
-
- @Override
- public void showDesktopApp(int taskId) throws RemoteException {
- // TODO
- }
-
- @Override
- public int getVisibleTaskCount(int displayId) throws RemoteException {
- int[] result = new int[1];
- executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
- controller -> result[0] = controller.getVisibleTaskCount(displayId),
- true /* blocking */
- );
- return result[0];
- }
-
- @Override
- public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
-
- }
-
- @Override
- public void stashDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void hideStashedDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
- // TODO(b/261234402): move visibility from sysui state to listener
- }
- }
-}
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 517f9f2aba27..dc82fc1b35dd 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
@@ -16,28 +16,19 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.Flags;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
- /**
- * Flag to indicate whether desktop mode is available on the device
- */
- private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode", false);
+ private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
/**
- * Flag to indicate whether desktop mode proto 2 is available on the device
+ * Flag to indicate whether desktop mode proto is available on the device
*/
private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_2", false);
@@ -64,28 +55,19 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_stashing", 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() {
+ public static boolean isEnabled() {
+ // Check for aconfig flag first
+ if (ENABLE_DESKTOP_WINDOWING) {
+ return true;
+ }
+ // Fall back to sysprop flag
+ // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
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();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -99,26 +81,4 @@ public class DesktopModeStatus {
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
- /**
- * Check if desktop mode is active
- *
- * @return {@code true} if active
- */
- public static boolean isActive(Context context) {
- 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);
- return result != 0;
- } catch (Exception e) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
- return false;
- }
- }
}
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 c05af73e6765..c0fc02fadd4d 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
@@ -52,8 +52,8 @@ class DesktopModeTaskRepository {
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>()
- // Track corners of desktop tasks, used to determine gesture exclusion
- private val desktopCorners = SparseArray<Region>()
+ // Track corner/caption regions of desktop tasks, used to determine gesture exclusion
+ private val desktopExclusionRegions = SparseArray<Region>()
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -96,10 +96,11 @@ class DesktopModeTaskRepository {
}
/**
- * Add a Consumer which will inform other classes of changes to corners for all Desktop tasks.
+ * Add a Consumer which will inform other classes of changes to exclusion regions for all
+ * Desktop tasks.
*/
- fun setTaskCornerListener(cornersListener: Consumer<Region>, executor: Executor) {
- desktopGestureExclusionListener = cornersListener
+ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
+ desktopGestureExclusionListener = regionListener
desktopGestureExclusionExecutor = executor
executor.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
@@ -107,14 +108,14 @@ class DesktopModeTaskRepository {
}
/**
- * Create a new merged region representative of all corners in all desktop tasks.
+ * Create a new merged region representative of all exclusion regions in all desktop tasks.
*/
private fun calculateDesktopExclusionRegion(): Region {
- val desktopCornersRegion = Region()
- desktopCorners.valueIterator().forEach { taskCorners ->
- desktopCornersRegion.op(taskCorners, Region.Op.UNION)
+ val desktopExclusionRegion = Region()
+ desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
+ desktopExclusionRegion.op(taskExclusionRegion, Region.Op.UNION)
}
- return desktopCornersRegion
+ return desktopExclusionRegion
}
/**
@@ -294,22 +295,24 @@ class DesktopModeTaskRepository {
}
/**
- * Updates the active desktop corners; if desktopCorners has been accepted by
- * desktopCornersListener, it will be updated in the appropriate classes.
+ * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
+ * accepted by desktopGestureExclusionListener, it will be updated in the
+ * appropriate classes.
*/
- fun updateTaskCorners(taskId: Int, taskCorners: Region) {
- desktopCorners.put(taskId, taskCorners)
+ fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
+ desktopExclusionRegions.put(taskId, taskExclusionRegions)
desktopGestureExclusionExecutor?.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
}
}
/**
- * Removes the active desktop corners for the specified task; if desktopCorners has been
- * accepted by desktopCornersListener, it will be updated in the appropriate classes.
+ * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion
+ * has been accepted by desktopGestureExclusionListener, it will be updated in the
+ * appropriate classes.
*/
- fun removeTaskCorners(taskId: Int) {
- desktopCorners.delete(taskId)
+ fun removeExclusionRegion(taskId: Int) {
+ desktopExclusionRegions.delete(taskId)
desktopGestureExclusionExecutor?.execute {
desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4e418e11d226..412a5b5a6997 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,9 +16,7 @@
package com.android.wm.shell.desktopmode
-import android.R
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -27,7 +25,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
-import android.content.res.TypedArray
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -40,10 +37,12 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.RemoteTransition
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -61,11 +60,15 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
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.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
@@ -91,6 +94,7 @@ class DesktopTasksController(
ToggleResizeDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
+ private val recentsTransitionHandler: RecentsTransitionHandler,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -109,18 +113,22 @@ class DesktopTasksController(
private val transitionAreaHeight
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+ )
private val transitionAreaWidth
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width)
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
+ )
+
+ private var recentsAnimationRunning = false
// This is public to avoid cyclic dependency; it is set by SplitScreenController
lateinit var splitScreenController: SplitScreenController
init {
desktopMode = DesktopModeImpl()
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
shellInit.addInitCallback({ onInit() }, this)
}
}
@@ -135,23 +143,38 @@ class DesktopTasksController(
)
transitions.addHandler(this)
desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
+
+ recentsTransitionHandler.addTransitionStateListener(
+ object : RecentsTransitionStateListener {
+ override fun onAnimationStateChanged(running: Boolean) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: recents animation state changed running=%b",
+ running
+ )
+ recentsAnimationRunning = running
+ }
+ }
+ )
}
/** Show all tasks, that are part of the desktop, on top of launcher */
- fun showDesktopApps(displayId: Int) {
+ fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
- // TODO(b/278084491): pass in display id
bringDesktopAppsToFront(displayId, wct)
- // Execute transaction if there are pending operations
- if (!wct.isEmpty) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/268662477): add animation for the transition
- transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
- } else {
- shellTaskOrganizer.applyTransaction(wct)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/255649902): ensure remote transition is supplied once state is introduced
+ val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+ val handler = remoteTransition?.let {
+ OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
+ }
+ transitions.startTransition(transitionType, wct, handler).also { t ->
+ handler?.setTransition(t)
}
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
}
}
@@ -212,6 +235,7 @@ class DesktopTasksController(
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
)
+ exitSplitIfApplicable(wct, task)
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
addMoveToDesktopChanges(wct, task)
@@ -239,6 +263,7 @@ class DesktopTasksController(
taskInfo.taskId
)
val wct = WindowContainerTransaction()
+ exitSplitIfApplicable(wct, taskInfo)
moveHomeTaskToFront(wct)
addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, startBounds)
@@ -318,7 +343,7 @@ class DesktopTasksController(
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setBounds(task.token, Rect())
wct.setDensityDpi(task.token, getDefaultDensityDpi())
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -328,6 +353,13 @@ class DesktopTasksController(
}
}
+ private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
+ if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ splitScreenController.prepareExitSplitScreen(wct,
+ splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP)
+ }
+ }
+
/**
* The second part of the animated move to desktop transition, called after
* {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
@@ -493,6 +525,55 @@ class DesktopTasksController(
}
}
+ /**
+ * Quick-resize to the right or left half of the stable bounds.
+ *
+ * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
+ */
+ fun snapToHalfScreen(
+ taskInfo: RunningTaskInfo,
+ windowDecor: DesktopModeWindowDecoration,
+ position: SnapPosition
+ ) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+
+ val destinationWidth = stableBounds.width() / 2
+ val destinationBounds = when (position) {
+ SnapPosition.LEFT -> {
+ Rect(
+ stableBounds.left,
+ stableBounds.top,
+ stableBounds.left + destinationWidth,
+ stableBounds.bottom
+ )
+ }
+ SnapPosition.RIGHT -> {
+ Rect(
+ stableBounds.right - destinationWidth,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom
+ )
+ }
+ }
+
+ if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
+
+ val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ toggleResizeDesktopTaskTransitionHandler.startTransition(
+ wct,
+ taskInfo.taskId,
+ windowDecor
+ )
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
@@ -580,6 +661,10 @@ class DesktopTasksController(
val triggerTask = request.triggerTask
val shouldHandleRequest =
when {
+ recentsAnimationRunning -> {
+ reason = "recents animation is running"
+ false
+ }
// Only handle open or to front transitions
request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
reason = "transition type not handled (${request.type})"
@@ -645,10 +730,7 @@ class DesktopTasksController(
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
- val ta: TypedArray = context.obtainStyledAttributes(
- intArrayOf(R.attr.dialogCornerRadius))
- val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
- ta.recycle()
+ val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
@@ -921,17 +1003,17 @@ class DesktopTasksController(
}
/**
- * Update the corner region for a specified task
+ * Update the exclusion region for a specified task
*/
- fun onTaskCornersChanged(taskId: Int, corner: Region) {
- desktopModeTaskRepository.updateTaskCorners(taskId, corner)
+ fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
+ desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
}
/**
- * Remove a previously tracked corner region for a specified task.
+ * Remove a previously tracked exclusion region for a specified task.
*/
- fun removeCornersForTask(taskId: Int) {
- desktopModeTaskRepository.removeTaskCorners(taskId)
+ fun removeExclusionRegionForTask(taskId: Int) {
+ desktopModeTaskRepository.removeExclusionRegion(taskId)
}
/**
@@ -945,16 +1027,16 @@ class DesktopTasksController(
}
/**
- * Adds a listener to track changes to desktop task corners
+ * Adds a listener to track changes to desktop task gesture exclusion regions
*
* @param listener the listener to add.
* @param callbackExecutor the executor to call the listener on.
*/
- fun setTaskCornerListener(
+ fun setTaskRegionListener(
listener: Consumer<Region>,
callbackExecutor: Executor
) {
- desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
+ desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
private fun dump(pw: PrintWriter, prefix: String) {
@@ -980,7 +1062,7 @@ class DesktopTasksController(
callbackExecutor: Executor
) {
mainExecutor.execute {
- this@DesktopTasksController.setTaskCornerListener(listener, callbackExecutor)
+ this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
}
@@ -1037,11 +1119,11 @@ class DesktopTasksController(
controller = null
}
- override fun showDesktopApps(displayId: Int) {
+ override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) {
ExecutorUtils.executeRemoteCallWithTaskPermission(
controller,
"showDesktopApps"
- ) { c -> c.showDesktopApps(displayId) }
+ ) { c -> c.showDesktopApps(displayId, remoteTransition) }
}
override fun stashDesktopApps(displayId: Int) {
@@ -1117,4 +1199,7 @@ class DesktopTasksController(
return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
}
}
+
+ /** The positions on a screen that a task can snap to. */
+ enum class SnapPosition { RIGHT, LEFT }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 1128d91bb5ad..024465b281b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -271,8 +271,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
startT.apply();
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null));
+ mTransitions.getMainExecutor().execute(() -> finishCallback.onTransitionFinished(null));
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 47edfd455f5a..6bdaf1eadb8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode;
import android.app.ActivityManager.RunningTaskInfo;
+import android.window.RemoteTransition;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
/**
@@ -25,7 +26,7 @@ import com.android.wm.shell.desktopmode.IDesktopTaskListener;
interface IDesktopMode {
/** Show apps on the desktop on the given display */
- void showDesktopApps(int displayId);
+ void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
/** Stash apps on the desktop to allow launching another app from home screen */
void stashDesktopApps(int displayId);
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 22541bbd892a..a80241e0ac5c 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
@@ -68,7 +68,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mShellTaskOrganizer.addFocusListener(this);
}
}
@@ -90,7 +90,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
t.apply();
}
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
if (taskInfo.isVisible) {
@@ -111,7 +111,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
@@ -135,7 +135,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId);
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -154,7 +154,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
- if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+ if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
new file mode 100644
index 000000000000..f7977f88006e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.performance
+
+import android.content.Context
+import android.os.PerformanceHintManager
+import android.os.Process
+import android.window.SystemPerformanceHinter
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import java.io.PrintWriter
+import java.util.concurrent.TimeUnit
+
+/**
+ * Manages the performance hints to the system.
+ */
+class PerfHintController(private val mContext: Context,
+ shellInit: ShellInit,
+ private val mShellCommandHandler: ShellCommandHandler,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer) {
+
+ // The system perf hinter
+ val hinter: SystemPerformanceHinter
+
+ init {
+ hinter = SystemPerformanceHinter(mContext,
+ rootTdaOrganizer.performanceRootProvider)
+ shellInit.addInitCallback(this::onInit, this)
+ }
+
+ private fun onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this)
+ val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java)
+ val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()),
+ TimeUnit.SECONDS.toNanos(1))
+ hinter.setAdpfSession(adpfSession)
+ }
+
+ fun dump(pw: PrintWriter, prefix: String?) {
+ hinter.dump(pw, prefix)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index bc17ce97dbff..118ad9c4bfe3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -383,6 +383,9 @@ public class PipAccessibilityInteractionConnection {
}
@Override
- public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
+ public void attachAccessibilityOverlayToWindow(
+ SurfaceControl sc,
+ int interactionId,
+ IAccessibilityInteractionConnectionCallback callback) {}
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 106486714a5c..63f20fd8e997 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
@@ -797,21 +797,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
mPipBoundsState.getStashedState());
- // Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged =
- mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
- && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
- != layout.densityDpi());
- if (densityDpiChanged) {
- final float scale = (float) layout.densityDpi()
- / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
- postChangeBounds.set(0, 0,
- (int) (postChangeBounds.width() * scale),
- (int) (postChangeBounds.height() * scale));
- }
-
updateDisplayLayout.run();
+ // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+ // example, if PiP was resized to 90% of the maximum size on the previous layout,
+ // make sure it is 90% of the new maximum size spec.
+ postChangeBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
// Calculate the PiP bounds in the new orientation based on same fraction along the
// rotated movement bounds.
final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -822,6 +816,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipDisplayLayoutState.getDisplayBounds(),
mPipDisplayLayoutState.getDisplayLayout().stableInsets());
+ // make sure we user resize to the updated bounds to avoid animating to any outdated
+ // sizes from the previous layout upon double tap CUJ
+ mPipBoundsState.setHasUserResizedPip(true);
+ mTouchHandler.setUserResizeBounds(postChangeBounds);
+
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
// the app content in PiP mode may or may not be updated for the new density dpi.
@@ -833,15 +836,6 @@ 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();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 8e3376f163c1..f6cab485fa2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -139,7 +139,7 @@ public class PipInputConsumer {
final InputChannel inputChannel = new InputChannel();
try {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -163,7 +163,7 @@ public class PipInputConsumer {
}
try {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
- mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to destroy input consumer, %s", TAG, e);
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 e5f9fdc7a740..f175775ce8b2 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
@@ -15,7 +15,6 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
@@ -31,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Looper;
-import android.provider.DeviceConfig;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -155,21 +153,8 @@ public class PipResizeGestureHandler {
mContext.getDisplay().getRealSize(mMaxSize);
reloadResources();
- mEnablePinchResize = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- PIP_PINCH_RESIZE,
- /* defaultValue = */ true);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mMainExecutor,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
- mEnablePinchResize = properties.getBoolean(
- PIP_PINCH_RESIZE, /* defaultValue = */ true);
- }
- }
- });
+ final Resources res = mContext.getResources();
+ mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
}
public void onConfigurationChanged() {
@@ -579,6 +564,12 @@ public class PipResizeGestureHandler {
resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
}
+ // If user resize is smaller than min size, auto resize to min
+ if (mLastResizeBounds.width() < mMinSize.x
+ || mLastResizeBounds.height() < mMinSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ }
+
// get the current movement bounds
final Rect movementBounds = mPipBoundsAlgorithm
.getMovementBounds(mLastResizeBounds);
@@ -679,6 +670,8 @@ public class PipResizeGestureHandler {
pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
}
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
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 2ce4fb9e297b..452a41696fcf 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
@@ -779,13 +779,10 @@ 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
+ * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
*/
- void userResizeTo(Rect bounds, float snapFraction) {
- mPipResizeGestureHandler.userResizeTo(bounds, snapFraction);
+ void setUserResizeBounds(Rect bounds) {
+ mPipResizeGestureHandler.setUserResizeBounds(bounds);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
index 5f6b3fe1e250..fc0b876e1bde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -77,6 +77,19 @@ abstract class TvPipAction {
return mActionType;
}
+ static String getActionTypeString(@ActionType int actionType) {
+ switch (actionType) {
+ case ACTION_FULLSCREEN: return "ACTION_FULLSCREEN";
+ case ACTION_CLOSE: return "ACTION_CLOSE";
+ case ACTION_MOVE: return "ACTION_MOVE";
+ case ACTION_EXPAND_COLLAPSE: return "ACTION_EXPAND_COLLAPSE";
+ case ACTION_CUSTOM: return "ACTION_CUSTOM";
+ case ACTION_CUSTOM_CLOSE: return "ACTION_CUSTOM_CLOSE";
+ default:
+ return "UNDEFINED";
+ }
+ }
+
abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
abstract PendingIntent getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 4bba9690707a..6b890c49b713 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -56,8 +56,10 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
private final List<Listener> mListeners = new ArrayList<>();
private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
- private final List<TvPipAction> mActionsList;
+ private final List<TvPipAction> mActionsList = new ArrayList<>();
+ private final TvPipSystemAction mFullscreenAction;
private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mMoveAction;
private final TvPipSystemAction mExpandCollapseAction;
private final List<RemoteAction> mMediaActions = new ArrayList<>();
@@ -67,26 +69,27 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
TvPipAction.SystemActionsHandler systemActionsHandler) {
mSystemActionsHandler = systemActionsHandler;
- mActionsList = new ArrayList<>();
- mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ mFullscreenAction = new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
- mSystemActionsHandler));
-
+ mSystemActionsHandler);
mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
- mActionsList.add(mDefaultCloseAction);
-
- mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
- R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
-
+ mMoveAction = new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler);
mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
mSystemActionsHandler);
- mActionsList.add(mExpandCollapseAction);
+ initActions();
pipMediaController.addActionListener(this::onMediaActionsChanged);
}
+ private void initActions() {
+ mActionsList.add(mFullscreenAction);
+ mActionsList.add(mDefaultCloseAction);
+ mActionsList.add(mMoveAction);
+ }
+
@Override
public void executeAction(@TvPipAction.ActionType int actionType) {
if (mSystemActionsHandler != null) {
@@ -199,6 +202,14 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
}
}
+ void reset() {
+ mActionsList.clear();
+ mMediaActions.clear();
+ mAppActions.clear();
+
+ initActions();
+ }
+
List<TvPipAction> getActionsList() {
return mActionsList;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 47a8cc89559e..2b3a93e3c3e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -81,6 +81,7 @@ public class TvPipBoundsState extends PipBoundsState {
super(context, sizeSpecSource, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
+ mTvPipGravity = mDefaultGravity;
mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46336ce0655a..72115fdefa05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -478,6 +478,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mActionBroadcastReceiver.unregister();
mTvPipMenuController.closeMenu();
+ mTvPipActionsProvider.reset();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.reset();
setState(STATE_NO_PIP);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b2a189b45d6c..ee55211a73a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -62,13 +62,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private TvPipBackgroundView mPipBackgroundView;
- private boolean mMenuIsFocused;
@TvPipMenuMode
private int mCurrentMenuMode = MODE_NO_MENU;
@TvPipMenuMode
private int mPrevMenuMode = MODE_NO_MENU;
+ /** When the window gains focus, enter this menu mode */
+ @TvPipMenuMode
+ private int mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+
@IntDef(prefix = { "MODE_" }, value = {
MODE_NO_MENU,
MODE_MOVE_MENU,
@@ -170,6 +173,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView = createTvPipMenuView();
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+ mPipMenuView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
+ onPipWindowFocusChanged(hasFocus);
+ });
}
@VisibleForTesting
@@ -224,13 +230,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementMenu()", TAG);
- switchToMenuMode(MODE_MOVE_MENU);
+ requestMenuMode(MODE_MOVE_MENU);
}
@Override
public void showMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
+ mPipMenuView.resetMenu();
+ requestMenuMode(MODE_ALL_ACTIONS_MENU);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -250,7 +257,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void closeMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
- switchToMenuMode(MODE_NO_MENU);
+ requestMenuMode(MODE_NO_MENU);
}
@Override
@@ -392,11 +399,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
}
- // Start methods handling {@link TvPipMenuMode}
+ // Beginning of convenience methods for {@link TvPipMenuMode}
@VisibleForTesting
boolean isMenuOpen() {
- return mCurrentMenuMode != MODE_NO_MENU;
+ return isMenuOpen(mCurrentMenuMode);
+ }
+
+ private static boolean isMenuOpen(@TvPipMenuMode int menuMode) {
+ return menuMode != MODE_NO_MENU;
}
@VisibleForTesting
@@ -409,31 +420,93 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
}
- private void switchToMenuMode(@TvPipMenuMode int menuMode) {
- switchToMenuMode(menuMode, false);
+ @VisibleForTesting
+ String getMenuModeString() {
+ return getMenuModeString(mCurrentMenuMode);
}
- private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
- // Note: we intentionally don't return early here, because the TvPipMenuView needs to
- // refresh the Ui even if there is no menu mode change.
- mPrevMenuMode = mCurrentMenuMode;
- mCurrentMenuMode = menuMode;
+ static String getMenuModeString(@TvPipMenuMode int menuMode) {
+ switch(menuMode) {
+ case MODE_NO_MENU:
+ return "MODE_NO_MENU";
+ case MODE_MOVE_MENU:
+ return "MODE_MOVE_MENU";
+ case MODE_ALL_ACTIONS_MENU:
+ return "MODE_ALL_ACTIONS_MENU";
+ default:
+ return "Unknown";
+ }
+ }
+
+ // Beginning of methods handling switching between menu modes
+
+ private void requestMenuMode(@TvPipMenuMode int menuMode) {
+ if (isMenuOpen() == isMenuOpen(menuMode)) {
+ // No need to request a focus change. We can directly switch to the new mode.
+ switchToMenuMode(menuMode);
+ } else {
+ if (isMenuOpen(menuMode)) {
+ mMenuModeOnFocus = menuMode;
+ }
+
+ // Send a request to gain window focus if the menu is open, or lose window focus
+ // otherwise. Once the focus change happens, we will request the new mode in the
+ // callback {@link #onPipWindowFocusChanged}.
+ requestPipMenuFocus(isMenuOpen(menuMode));
+ }
+ // Note: we don't handle cases where there is a focus change currently in flight, because
+ // this is very unlikely to happen in practice and would complicate the logic.
+ }
+
+ private void requestPipMenuFocus(boolean focus) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: requestPipMenuFocus(%b)", TAG, focus);
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mSystemWindows.getFocusGrantToken(mPipMenuView), focus);
+ } catch (Exception e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus, %s", TAG, e);
+ }
+ }
+
+ /**
+ * Called when the menu window gains or loses focus.
+ */
+ @VisibleForTesting
+ void onPipWindowFocusChanged(boolean focused) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+ switchToMenuMode(focused ? mMenuModeOnFocus : MODE_NO_MENU);
+
+ // Reset the default menu mode for focused state.
+ mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
+ }
+
+ /**
+ * Immediately switches to the menu mode in the given request. Updates the mDelegate and the UI.
+ * Doesn't handle any focus changes.
+ */
+ private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: switchToMenuMode: from=%s, to=%s", TAG, getMenuModeString(),
+ getMenuModeString(menuMode));
- ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
- getMenuModeString(), getMenuModeString(mPrevMenuMode));
+ if (mCurrentMenuMode == menuMode) return;
- updateUiOnNewMenuModeRequest(resetMenu);
+ mPrevMenuMode = mCurrentMenuMode;
+ mCurrentMenuMode = menuMode;
+ updateUiOnNewMenuModeRequest();
updateDelegateOnNewMenuModeRequest();
}
- private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
+ private void updateUiOnNewMenuModeRequest() {
if (mPipMenuView == null || mPipBackgroundView == null) return;
mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
- mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+ mPipMenuView.transitionToMenuMode(mCurrentMenuMode);
mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
- grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
}
private void updateDelegateOnNewMenuModeRequest() {
@@ -444,29 +517,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mDelegate.onInMoveModeChanged();
}
- if (mCurrentMenuMode == MODE_NO_MENU) {
+ if (!isMenuOpen()) {
mDelegate.onMenuClosed();
}
}
- @VisibleForTesting
- String getMenuModeString() {
- return getMenuModeString(mCurrentMenuMode);
- }
-
- static String getMenuModeString(@TvPipMenuMode int menuMode) {
- switch(menuMode) {
- case MODE_NO_MENU:
- return "MODE_NO_MENU";
- case MODE_MOVE_MENU:
- return "MODE_MOVE_MENU";
- case MODE_ALL_ACTIONS_MENU:
- return "MODE_ALL_ACTIONS_MENU";
- default:
- return "Unknown";
- }
- }
-
// Start {@link TvPipMenuView.Delegate} methods
@Override
@@ -476,42 +531,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onBackPress() {
- if (!onExitMoveMode()) {
- closeMenu();
- }
- }
-
- @Override
- public boolean onExitMoveMode() {
+ public void onExitCurrentMenuMode() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
-
- final int saveMenuMode = mCurrentMenuMode;
- if (isInMoveMode()) {
- switchToMenuMode(mPrevMenuMode);
- }
- return saveMenuMode == MODE_MOVE_MENU;
+ "%s: onExitCurrentMenuMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+ requestMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU);
}
@Override
- public boolean onPipMovement(int keycode) {
+ public void onPipMovement(int keycode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
if (isInMoveMode()) {
mDelegate.movePip(keycode);
}
- return isInMoveMode();
- }
-
- @Override
- public void onPipWindowFocusChanged(boolean focused) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
- mMenuIsFocused = focused;
- if (!focused && isMenuOpen()) {
- closeMenu();
- }
}
interface Delegate {
@@ -524,21 +556,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void closeEduText();
}
- private void grantPipMenuFocus(boolean grantFocus) {
- if (mMenuIsFocused == grantFocus) return;
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: grantWindowFocus(%b)", TAG, grantFocus);
-
- try {
- WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
- mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
- } catch (Exception e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Unable to update focus, %s", TAG, e);
- }
- }
-
private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
private final View mView;
private final int mZOrder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 45e1cde8f9a9..57439a59ccca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -328,7 +328,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
return menuUiBounds;
}
- void transitionToMenuMode(int menuMode, boolean resetMenu) {
+ void transitionToMenuMode(int menuMode) {
switch (menuMode) {
case MODE_NO_MENU:
hideAllUserControls();
@@ -337,7 +337,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
showMoveMenu();
break;
case MODE_ALL_ACTIONS_MENU:
- showAllActionsMenu(resetMenu);
+ showAllActionsMenu();
break;
default:
throw new IllegalArgumentException(
@@ -362,13 +362,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.closeIfNeeded();
}
- private void showAllActionsMenu(boolean resetMenu) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+ void resetMenu() {
+ scrollToFirstAction();
+ }
- if (resetMenu) {
- scrollToFirstAction();
- }
+ private void showAllActionsMenu() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showAllActionsMenu()", TAG);
if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
@@ -431,12 +431,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
}
}
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- mListener.onPipWindowFocusChanged(hasWindowFocus);
- }
-
private void animateAlphaTo(float alpha, View view) {
if (view.getAlpha() == alpha) {
return;
@@ -483,28 +477,28 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
if (event.getAction() == ACTION_UP) {
if (event.getKeyCode() == KEYCODE_BACK) {
- mListener.onBackPress();
+ mListener.onExitCurrentMenuMode();
return true;
}
- if (mA11yManager.isEnabled()) {
- return super.dispatchKeyEvent(event);
- }
-
- switch (event.getKeyCode()) {
- case KEYCODE_DPAD_UP:
- case KEYCODE_DPAD_DOWN:
- case KEYCODE_DPAD_LEFT:
- case KEYCODE_DPAD_RIGHT:
- return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
- event);
- case KEYCODE_ENTER:
- case KEYCODE_DPAD_CENTER:
- return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
- default:
- break;
+ if (mCurrentMenuMode == MODE_MOVE_MENU && !mA11yManager.isEnabled()) {
+ switch (event.getKeyCode()) {
+ case KEYCODE_DPAD_UP:
+ case KEYCODE_DPAD_DOWN:
+ case KEYCODE_DPAD_LEFT:
+ case KEYCODE_DPAD_RIGHT:
+ mListener.onPipMovement(event.getKeyCode());
+ return true;
+ case KEYCODE_ENTER:
+ case KEYCODE_DPAD_CENTER:
+ mListener.onExitCurrentMenuMode();
+ return true;
+ default:
+ // Dispatch key event as normal below
+ }
}
}
+
return super.dispatchKeyEvent(event);
}
@@ -529,7 +523,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
if (a11yEnabled) {
mA11yDoneButton.setVisibility(VISIBLE);
mA11yDoneButton.setOnClickListener(v -> {
- mListener.onExitMoveMode();
+ mListener.onExitCurrentMenuMode();
});
mA11yDoneButton.requestFocus();
mA11yDoneButton.requestAccessibilityFocus();
@@ -626,26 +620,15 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
interface Listener {
- void onBackPress();
-
- /**
- * Called when a button for exiting move mode was pressed.
- *
- * @return true if the event was handled or false if the key event should be handled by the
- * next receiver.
- */
- boolean onExitMoveMode();
-
/**
- * @return whether pip movement was handled.
+ * Called when a button for exiting the current menu mode was pressed.
*/
- boolean onPipMovement(int keycode);
+ void onExitCurrentMenuMode();
/**
- * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
- * has lost focus.
+ * Called when a button to move the PiP in a certain direction, indicated by keycode.
*/
- void onPipWindowFocusChanged(boolean focused);
+ void onPipMovement(int keycode);
/**
* The edu text closing impacts the size of the Picture-in-Picture window and influences
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index ec09827fa4d1..6dabb3bf6f9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,3 +1,4 @@
# WM shell sub-module pip owner
hwwang@google.com
mateuszc@google.com
+gabiyev@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 000000000000..186cb615f4ec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+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.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final String TAG = PipController.class.getSimpleName();
+
+ private Context mContext;
+ private ShellController mShellController;
+ private DisplayController mDisplayController;
+ private DisplayInsetsController mDisplayInsetsController;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
+ private PipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ mContext = context;
+ mShellController = shellController;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Ensure that we have the display info in case we get calls to update the bounds before the
+ // listener calls back
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+
+ mShellController.addConfigurationChangeListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ onDisplayChanged(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+ }
+ });
+ }
+
+ /**
+ * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+ */
+ public static PipController create(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Device doesn't support Pip feature", TAG);
+ return null;
+ }
+ return new PipController(context, shellInit, shellController, displayController,
+ displayInsetsController, pipDisplayLayoutState);
+ }
+
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mPipDisplayLayoutState.onConfigurationChanged();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ private void onDisplayChanged(DisplayLayout layout) {
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index b8e4c04ac262..d704b091754f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -14,9 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip2;
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.WindowManager.TRANSIT_OPEN;
import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -34,8 +39,13 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-/** Placeholder, for demonstrate purpose only. */
+/**
+ * Implementation of transitions for PiP on phone.
+ */
public class PipTransition extends PipTransitionController {
+ @Nullable
+ private IBinder mAutoEnterButtonNavTransition;
+
public PipTransition(
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -58,15 +68,63 @@ public class PipTransition extends PipTransitionController {
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
+ if (isAutoEnterInButtonNavigation(request)) {
+ mAutoEnterButtonNavTransition = transition;
+ return getEnterPipTransaction(transition, request);
+ }
return null;
}
@Override
+ public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWct) {
+ if (isAutoEnterInButtonNavigation(request)) {
+ outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
+ mAutoEnterButtonNavTransition = transition;
+ }
+ }
+
+ private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+ PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+ pipParams, mPipBoundsAlgorithm);
+
+ // calculate the entry bounds and notify core to move task to pinned with final bounds
+ final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+ return wct;
+ }
+
+ private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+ final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+ if (pipTask == null) {
+ return false;
+ }
+ if (pipTask.pictureInPictureParams == null) {
+ return false;
+ }
+
+ // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+ // implies that we are entering PiP in button navigation mode. This is guaranteed by
+ // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+ return requestInfo.getType() == TRANSIT_OPEN
+ && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+ }
+
+ @Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition == mAutoEnterButtonNavTransition) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
return false;
}
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 069066e4bd49..2616b8b08bf1 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
@@ -34,4 +34,10 @@ public interface RecentTasks {
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
}
+
+ /**
+ * Adds the listener to be notified of whether the recent task animation is running.
+ */
+ default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
+ }
}
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 f35eda6caef0..ccc34389557c 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
@@ -203,6 +203,17 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
+ @Nullable
+ public SplitBounds getSplitBoundsForTaskId(int taskId) {
+ if (taskId == INVALID_TASK_ID) {
+ return null;
+ }
+
+ // We could do extra verification of requiring both taskIds of a pair and verifying that
+ // the same split bounds object is returned... but meh. Seems unnecessary.
+ return mTaskSplitBoundsMap.get(taskId);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -329,7 +340,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
continue;
}
- if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
+ if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
freeformTasks.add(taskInfo);
@@ -417,6 +428,21 @@ public class RecentTasksController implements TaskStackListenerCallback,
executor.execute(() -> callback.accept(tasks));
});
}
+
+ @Override
+ public void addAnimationStateListener(Executor executor, Consumer<Boolean> listener) {
+ mMainExecutor.execute(() -> {
+ if (mTransitionHandler == null) {
+ return;
+ }
+ mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+ @Override
+ public void onAnimationStateChanged(boolean running) {
+ executor.execute(() -> listener.accept(running));
+ }
+ });
+ });
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 50ba8975802b..d277eef761e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.recents;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -23,6 +24,8 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -51,6 +54,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -69,8 +73,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private final Transitions mTransitions;
private final ShellExecutor mExecutor;
+ @Nullable
+ private final RecentTasksController mRecentTasksController;
private IApplicationThread mAnimApp = null;
private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+ private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>();
/**
* List of other handlers which might need to mix recents with other things. These are checked
@@ -82,6 +89,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Nullable RecentTasksController recentTasksController) {
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
+ mRecentTasksController = recentTasksController;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
shellInit.addInitCallback(() -> {
@@ -100,6 +108,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mMixers.remove(mixer);
}
+ /** Adds the callback for receiving the state change of transition. */
+ public void addTransitionStateListener(RecentsTransitionStateListener listener) {
+ mStateListeners.add(listener);
+ }
+
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -122,6 +135,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
mixedHandler == null ? this : mixedHandler);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onTransitionStarted(transition);
+ }
if (mixer != null) {
mixer.setRecentsTransition(transition);
}
@@ -165,13 +181,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return false;
}
final RecentsController controller = mControllers.get(controllerIdx);
- Transitions.setRunningRemoteTransitionDelegate(mAnimApp);
+ final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: failed to start animation");
return false;
}
+ Transitions.setRunningRemoteTransitionDelegate(animApp);
return true;
}
@@ -263,7 +280,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */);
+ finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -297,7 +314,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */);
} else {
cleanUp();
}
@@ -382,6 +399,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mTransition = null;
mPendingPauseSnapshotsForCancel = null;
mControllers.remove(this);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(false);
+ }
}
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
@@ -426,6 +446,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+ int closingSplitTaskId = INVALID_TASK_ID;
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
@@ -452,6 +473,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
apps.add(target);
if (TransitionUtil.isClosingType(change.getMode())) {
mPausingTasks.add(new TaskState(change, target.leash));
+ closingSplitTaskId = change.getTaskInfo().taskId;
if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" adding pausing leaf home taskId=%d", taskInfo.taskId);
@@ -509,13 +531,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
t.apply();
+ Bundle b = new Bundle(1 /*capacity*/);
+ b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
+ mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.start: calling onAnimationStart", mInstanceId);
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
- new Rect(0, 0, 0, 0), new Rect());
+ new Rect(0, 0, 0, 0), new Rect(), b);
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Error starting recents animation", e);
cancel("onAnimationStart() failed");
@@ -643,7 +671,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
// now and let it do its animation (since recents is going to be occluded).
sendCancelWithSnapshots();
mExecutor.executeDelayed(
- () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0);
+ () -> finishInner(true /* toHome */, false /* userLeaveHint */,
+ null /* finishCb */), 0);
return;
}
if (recentsOpening != null) {
@@ -872,11 +901,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
@SuppressLint("NewApi")
- public void finish(boolean toHome, boolean sendUserLeaveHint) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint));
+ public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
}
- private void finishInner(boolean toHome, boolean sendUserLeaveHint) {
+ private void finishInner(boolean toHome, boolean sendUserLeaveHint,
+ IResultReceiver runnerFinishCb) {
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
@@ -895,19 +925,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome
- // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
- // live tile (pausing app). If the gesture is "cancelled" we need to return to
- // 3p launcher instead of "task-switching" away from it.
- && (!mWillFinishToHome || mPausingSeparateHome)
- && mPausingTasks != null && mState == STATE_NORMAL) {
- if (mPausingSeparateHome) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to 3p home");
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to app");
- }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -939,6 +958,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ if (mPausingSeparateHome) {
+ if (mOpeningTasks.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " recents occluded 3p home");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " switch task by recents on 3p home");
+ }
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
@@ -966,6 +994,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
cleanUp();
finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
+ if (runnerFinishCb != null) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: calling finish callback",
+ mInstanceId);
+ runnerFinishCb.send(0, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report transition finished", e);
+ }
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index a401cb494822..e8733ebd8f03 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.bubble
+package com.android.wm.shell.recents;
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import android.os.IBinder;
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) :
- SendBubbleNotificationTest(flicker)
+/** The listener for the events from {@link RecentsTransitionHandler}. */
+public interface RecentsTransitionStateListener {
+
+ /** Notifies whether the recents animation is running. */
+ default void onAnimationStateChanged(boolean running) {
+ }
+
+ /** Notifies that a recents shell transition has started. */
+ default void onTransitionStarted(IBinder transition) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 14304a3c0aac..253acc49071a 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
@@ -91,42 +91,42 @@ interface ISplitScreen {
* Starts tasks simultaneously in one transition.
*/
oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ int splitPosition, int snapPosition, in RemoteTransition remoteTransition,
in InstanceId instanceId) = 10;
/**
* Starts a pair of intent and task in one transition.
*/
oneway void startIntentAndTask(in PendingIntent pendingIntent, int userId1, in Bundle options1,
- int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ int taskId, in Bundle options2, int sidePosition, int snapPosition,
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 Bundle options2, int splitPosition, int snapPosition,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
- in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options2, int splitPosition, int snapPosition,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, int userId1,
- in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
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 Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
@@ -135,7 +135,7 @@ interface ISplitScreen {
oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1, int userId1,
in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
+ int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
@@ -143,7 +143,7 @@ interface ISplitScreen {
oneway void startIntents(in PendingIntent pendingIntent1, int userId1,
in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
- float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+ int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index f90ee586e696..664d44910e72 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
@@ -43,6 +43,7 @@ import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -85,6 +86,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -130,6 +132,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
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;
+ public static final int EXIT_REASON_ENTER_DESKTOP = 12;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -143,6 +146,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
EXIT_REASON_RECREATE_SPLIT,
EXIT_REASON_FULLSCREEN_SHORTCUT,
+ EXIT_REASON_ENTER_DESKTOP
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -598,8 +602,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (options1 == null) options1 = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
@@ -623,13 +627,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
- activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+ activityOptions.toBundle(), taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
if (options1 == null) options1 = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
final String packageName1 = shortcutInfo.getPackage();
@@ -656,7 +661,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
/**
@@ -671,8 +676,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
@@ -693,12 +698,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
- options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ options1, taskId, options2, splitPosition, snapPosition, adapter, instanceId);
}
private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
@@ -725,14 +730,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -756,14 +762,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, splitRatio, adapter, instanceId);
+ splitPosition, snapPosition, adapter, instanceId);
}
private void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -798,7 +805,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
- activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition,
+ activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition,
instanceId);
}
@@ -814,21 +821,22 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName1 = SplitScreenUtils.getPackageName(intent);
final String packageName2 = getPackageName(reverseSplitPosition(position));
final int userId2 = getUserId(reverseSplitPosition(position));
+ final ComponentName component = intent.getIntent().getComponent();
+
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background. If we don't explicitly reuse, new may be created even if the app
+ // isn't multi-instance because WM won't automatically remove/reuse the previous instance
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
- // 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(
- intent.getIntent().getComponent(), userId1))
- .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);
@@ -1009,6 +1017,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "CHILD_TASK_ENTER_PIP";
case EXIT_REASON_RECREATE_SPLIT:
return "RECREATE_SPLIT";
+ case EXIT_REASON_ENTER_DESKTOP:
+ return "ENTER_DESKTOP";
default:
return "unknown reason, reason int = " + exitReason;
}
@@ -1217,78 +1227,82 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- taskId1, options1, taskId2, options2, splitPosition,
- splitRatio, adapter, instanceId));
+ taskId1, options1, taskId2, options2, splitPosition, snapPosition,
+ adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
- Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
- userId1, options1, taskId, options2, splitPosition, splitRatio,
- adapter, instanceId));
+ userId1, options1, taskId, options2, splitPosition,
+ snapPosition, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.startShortcutAndTaskWithLegacyTransition(
shortcutInfo, options1, taskId, options2, splitPosition,
- splitRatio, adapter, instanceId));
+ snapPosition, adapter, instanceId));
}
@Override
public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
- taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ taskId2, options2, splitPosition, snapPosition, remoteTransition,
instanceId));
}
@Override
public void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
(controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1,
- taskId, options2, splitPosition, splitRatio, remoteTransition,
+ taskId, options2, splitPosition, snapPosition, 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) {
+ @PersistentSnapPosition int snapPosition,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
(controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId));
+ options2, splitPosition, snapPosition, remoteTransition, instanceId));
}
@Override
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
controller.startIntentsWithLegacyTransition(pendingIntent1, userId1,
shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2,
- options2, splitPosition, splitRatio, adapter, instanceId)
+ options2, splitPosition, snapPosition, adapter, instanceId)
);
}
@@ -1296,13 +1310,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntents",
(controller) ->
controller.startIntents(pendingIntent1, userId1, shortcutInfo1,
options1, pendingIntent2, userId2, shortcutInfo2, options2,
- splitPosition, splitRatio, remoteTransition, instanceId)
+ splitPosition, snapPosition, remoteTransition, instanceId)
);
}
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 5483fa5d29f6..f4ab2266179a 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
@@ -25,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
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__ENTER_DESKTOP;
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;
@@ -42,6 +43,7 @@ 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_ENTER_DESKTOP;
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;
@@ -192,6 +194,8 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
case EXIT_REASON_FULLSCREEN_SHORTCUT:
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+ case EXIT_REASON_ENTER_DESKTOP:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
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 1c792395d22d..7a4834cb5adb 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
@@ -129,6 +129,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
@@ -143,6 +144,8 @@ import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -639,8 +642,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
/** Starts 2 tasks in one transition. */
- void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
@@ -662,13 +665,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
- startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId2, options2, snapPosition, 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,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
@@ -684,13 +687,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, 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) {
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -705,7 +709,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
}
/**
@@ -716,14 +720,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
- @Nullable Bundle mainOptions, float splitRatio,
+ @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
mMainStage.activate(wct, false /* reparent */);
}
- mSplitLayout.setDivideRatio(splitRatio);
+ mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
@@ -750,7 +754,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
@@ -772,7 +776,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
setSideStagePosition(splitPosition, wct);
- mSplitLayout.setDivideRatio(splitRatio);
+ mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
@@ -802,7 +806,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** 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) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId2 == INVALID_TASK_ID) {
@@ -823,7 +828,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
- startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, snapPosition, adapter,
instanceId);
}
@@ -832,8 +837,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
@Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
@@ -852,13 +857,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
}
startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, splitRatio, adapter, instanceId);
+ splitPosition, snapPosition, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
@@ -871,15 +876,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
/** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
@@ -890,7 +895,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
}
@@ -940,18 +945,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId);
}
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
- splitRatio, adapter, instanceId);
+ snapPosition, adapter, instanceId);
}
/**
@@ -962,15 +968,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
+ 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);
+ mSplitLayout.setDivideRatio(snapPosition);
// Apply surface bounds before animation start.
SurfaceControl.Transaction startT = mTransactionPool.acquire();
@@ -1778,7 +1784,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
rightBottomTaskId = sideStageTopTaskId;
}
SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
- leftTopTaskId, rightBottomTaskId);
+ leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
@@ -2733,7 +2739,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
- shouldAnimate = startPendingEnterAnimation(
+ shouldAnimate = startPendingEnterAnimation(transition,
mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss;
@@ -2772,7 +2778,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- private boolean startPendingEnterAnimation(
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
@NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
@@ -2801,21 +2807,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- if (mSplitTransitions.mPendingEnter.mExtraTransitType
+ SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter;
+ if (pendingEnter.mExtraTransitType
== TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
// Open to side should only be used when split already active and foregorund.
if (mainChild == null && sideChild == null) {
Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
"Launched a task in split, but didn't receive any task in transition."));
// This should happen when the target app is already on front, so just cancel.
- mSplitTransitions.mPendingEnter.cancel(null);
+ pendingEnter.cancel(null);
return true;
}
} else {
if (mainChild == null || sideChild == null) {
final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
- mSplitTransitions.mPendingEnter.cancel(
+ pendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
"launched 2 tasks in split, but didn't receive "
@@ -2826,6 +2833,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mRecentTasks.isPresent() && sideChild != null) {
mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
}
+ if (pendingEnter.mRemoteHandler != null) {
+ // Pass false for aborted since WM didn't abort, business logic chose to
+ // terminate/exit early
+ pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
+ false /*aborted*/, finishT);
+ }
mSplitUnsupportedToast.show();
return true;
}
@@ -3155,6 +3168,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
}
+ @NeverCompile
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
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 20da8773f387..edb5aba1e46b 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
@@ -46,6 +46,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final int mIconFadeOutDuration;
private final int mAppRevealDelay;
private final int mAppRevealDuration;
+ @SplashScreenExitAnimationUtils.ExitAnimationType
+ private final int mAnimationType;
private final int mAnimationDuration;
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
@@ -91,6 +93,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
}
mAppRevealDuration = context.getResources().getInteger(
R.integer.starting_window_app_reveal_anim_duration);
+ mAnimationType = context.getResources().getInteger(
+ R.integer.starting_window_exit_animation_type);
mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
mMainWindowShiftLength = mainWindowShiftLength;
mFinishCallback = handleFinish;
@@ -98,10 +102,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
}
void startAnimations() {
- SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
- mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
- mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this, mRoundedCornerRadius);
+ SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView,
+ mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame,
+ mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha,
+ mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index a7e4385b60c8..e86b62dee86d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -20,6 +20,7 @@ import static android.view.Choreographer.CALLBACK_COMMIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
@@ -54,6 +55,7 @@ import com.android.wm.shell.common.TransactionPool;
public class SplashScreenExitAnimationUtils {
private static final boolean DEBUG_EXIT_ANIMATION = false;
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final boolean DEBUG_EXIT_FADE_ANIMATION = false;
private static final String TAG = "SplashScreenExitAnimationUtils";
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
@@ -62,20 +64,47 @@ public class SplashScreenExitAnimationUtils {
private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
/**
+ * This splash screen exit animation type uses a radial vanish to hide
+ * the starting window and slides up the main window content.
+ * @hide
+ */
+ public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
+
+ /**
+ * This splash screen exit animation type fades out the starting window
+ * to reveal the main window content.
+ * @hide
+ */
+ public static final int TYPE_FADE_OUT = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_RADIAL_VANISH_SLIDE_UP,
+ TYPE_FADE_OUT,
+ })
+ public @interface ExitAnimationType {}
+
+ /**
* 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);
+ static void startAnimations(@ExitAnimationType int animationType,
+ 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;
+ if (animationType == TYPE_FADE_OUT) {
+ animator = createFadeOutAnimation(splashScreenView, animationDuration,
+ iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay,
+ appRevealDuration, animatorListener);
+ } else {
+ animator = createRadialVanishSlideUpAnimator(splashScreenView,
+ firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+ animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+ appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+ }
animator.start();
}
@@ -89,17 +118,18 @@ public class SplashScreenExitAnimationUtils {
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 */);
+ // Start the default 'reveal' animation.
+ startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, 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,
+ private static ValueAnimator createRadialVanishSlideUpAnimator(ViewGroup splashScreenView,
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
@@ -210,6 +240,59 @@ public class SplashScreenExitAnimationUtils {
return nightMode == Configuration.UI_MODE_NIGHT_YES;
}
+ private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView,
+ int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+ float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+ Animator.AnimatorListener animatorListener) {
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ splashScreenView.setBackgroundColor(Color.BLUE);
+ }
+
+ final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(animationDuration);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.addUpdateListener(animation -> {
+
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Icon fade out progress (always starts immediately)
+ final float iconFadeProgress = 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 * (1f - iconFadeProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress));
+ }
+
+ // Splash screen fade out progress (possibly delayed)
+ final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation(
+ getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration));
+
+ splashScreenView.setAlpha(1f - splashFadeProgress);
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ Slog.d(TAG, "progress -> animation: " + linearProgress
+ + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a")
+ + "\t splash alpha: " + splashScreenView.getAlpha()
+ );
+ }
+ });
+ if (animatorListener != null) {
+ animator.addListener(animatorListener);
+ }
+ return animator;
+ }
+
/**
* View which creates a circular reveal of the underlying view.
* @hide
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 29be34347b22..0c6adc942385 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
@@ -112,28 +112,15 @@ public class SplashscreenContentDrawer {
*/
static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
- // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
- // icon which it's non-transparent foreground area is similar to it's background area, then
- // do not enlarge the foreground drawable.
- // For example, an icon with the foreground 108*108 opaque pixels and it's background
- // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
- private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
-
- /**
- * If the developer doesn't specify a background for the icon, we slightly scale it up.
- *
- * The background is either manually specified in the theme or the Adaptive Icon
- * background is used if it's different from the window background.
- */
- private static final float NO_BACKGROUND_SCALE = 192f / 160;
private final Context mContext;
private final HighResIconProvider mHighResIconProvider;
-
private int mIconSize;
private int mDefaultIconSize;
private int mBrandingImageWidth;
private int mBrandingImageHeight;
private int mMainWindowShiftLength;
+ private float mEnlargeForegroundIconThreshold;
+ private float mNoBackgroundScale;
private int mLastPackageContextConfigHash;
private final TransactionPool mTransactionPool;
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
@@ -336,6 +323,10 @@ public class SplashscreenContentDrawer {
com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
+ mEnlargeForegroundIconThreshold = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold);
+ mNoBackgroundScale = mContext.getResources().getFloat(
+ com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor);
}
/**
@@ -604,14 +595,14 @@ public class SplashscreenContentDrawer {
// There is no background below the icon, so scale the icon up
if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
|| mTmpAttrs.mIconBgColor == mThemeColor) {
- mFinalIconSize *= NO_BACKGROUND_SCALE;
+ mFinalIconSize *= mNoBackgroundScale;
}
createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
} else {
final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
final int scaledIconDpi =
- (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
+ (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
iconDrawable = mHighResIconProvider.getIcon(
mActivityInfo, densityDpi, scaledIconDpi);
@@ -693,8 +684,8 @@ public class SplashscreenContentDrawer {
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// scale by 192/160 if we only draw adaptiveIcon's foreground.
final float noBgScale =
- iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
- ? NO_BACKGROUND_SCALE : 1f;
+ iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold
+ ? mNoBackgroundScale : 1f;
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
// current settings.
mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 0d77a2e4610c..ef8393c3b5b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -29,12 +29,16 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewTreeObserver;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.concurrent.Executor;
/**
@@ -74,6 +78,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
private Insets mCaptionInsets;
+ private Handler mHandler;
public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -81,6 +86,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
// TODO(b/266736992): Think about a better way to set the TaskViewBase on the
// TaskViewTaskController and vice-versa
mTaskViewTaskController.setTaskViewBase(this);
+ mHandler = Handler.getMain();
getHolder().addCallback(this);
}
@@ -117,14 +123,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
onLocationChanged();
if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+ runOnViewThread(() -> setResizeBackgroundColor(bgColor));
}
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ final int bgColor = taskInfo.taskDescription.getBackgroundColor();
+ runOnViewThread(() -> setResizeBackgroundColor(bgColor));
}
}
@@ -143,7 +151,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
- setResizeBackgroundColor(t, bgColor);
+ runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
}
/**
@@ -272,12 +280,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mHandler = getHandler();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mHandler = Handler.getMain();
}
/** Returns the task info for the task in the TaskView. */
@@ -285,4 +295,24 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
public ActivityManager.RunningTaskInfo getTaskInfo() {
return mTaskViewTaskController.getTaskInfo();
}
+
+ /**
+ * Sets the handler, only for testing.
+ */
+ @VisibleForTesting
+ void setHandler(Handler viewHandler) {
+ mHandler = viewHandler;
+ }
+
+ /**
+ * Ensures that the given runnable runs on the view's thread.
+ */
+ private void runOnViewThread(Runnable r) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ r.run();
+ } else {
+ // If this call is not from the same thread as the view, then post it
+ mHandler.post(r);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index cefbb17d783d..e03f82526bdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -165,19 +165,6 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
return null;
}
- /**
- * Returns all the pending transitions for a given `taskView`.
- * @param taskView the pending transition should be for this.
- */
- ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) {
- ArrayList<PendingTransition> list = new ArrayList<>();
- for (int i = mPending.size() - 1; i >= 0; --i) {
- if (mPending.get(i).mTaskView != taskView) continue;
- list.add(mPending.get(i));
- }
- return list;
- }
-
private PendingTransition findPending(IBinder claimed) {
for (int i = 0; i < mPending.size(); ++i) {
if (mPending.get(i).mClaimed != claimed) continue;
@@ -273,10 +260,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// Task view isn't visible, the bounds will next visibility update.
return;
}
- PendingTransition pendingOpen = findPendingOpeningTransition(taskView);
- if (pendingOpen != null) {
- // There is already an opening transition in-flight, the window bounds will be
- // set in prepareOpenAnimation (via the window crop) if needed.
+ if (hasPending()) {
+ // There is already a transition in-flight, the window bounds will be set in
+ // prepareOpenAnimation.
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
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 f0bb665f8082..918a5a4bd53e 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
@@ -19,8 +19,11 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -42,8 +45,8 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
-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.keyguard.KeyguardTransitionHandler;
@@ -72,9 +75,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private final KeyguardTransitionHandler mKeyguardHandler;
- private DesktopModeController mDesktopModeController;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
+ private ActivityEmbeddingController mActivityEmbeddingController;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -94,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
/** Recents Transition while in desktop mode. */
static final int TYPE_RECENTS_DURING_DESKTOP = 6;
- /** Fuld/Unfold transition. */
+ /** Fold/Unfold transition. */
static final int TYPE_UNFOLD = 7;
+ /** Enter pip from one of the Activity Embedding windows. */
+ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -150,9 +156,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
- Optional<DesktopModeController> desktopModeControllerOptional,
Optional<DesktopTasksController> desktopTasksControllerOptional,
- Optional<UnfoldTransitionHandler> unfoldHandler) {
+ Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -170,9 +176,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mRecentsHandler != null) {
mRecentsHandler.addMixer(this);
}
- mDesktopModeController = desktopModeControllerOptional.orElse(null);
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
+ mActivityEmbeddingController = activityEmbeddingController.orElse(null);
}, this);
}
}
@@ -195,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getType() == TRANSIT_PIP
+ && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+ mActivityEmbeddingController != null)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Got a PiP-enter request from an Activity Embedding split");
+ mActiveTransitions.add(new MixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+ // Postpone transition splitting to later.
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
&& (request.getTriggerTask() == null
@@ -256,9 +272,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
- || DesktopModeStatus.isActive(mPlayer.getContext()))) {
- return this;
+ if (mRecentsHandler != null) {
+ if (mSplitHandler.isSplitScreenVisible()) {
+ return this;
+ } else if (mDesktopTasksController != null
+ // Check on the default display. Recents/gesture nav is only available there
+ && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ return this;
+ }
}
return null;
}
@@ -272,7 +293,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = mRecentsHandler;
mActiveTransitions.add(mixed);
- } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+ } else if (DesktopModeStatus.isEnabled()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "desktop mode is active, so treat it as Mixed.");
final MixedTransition mixed = new MixedTransition(
@@ -353,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -398,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
}
}
+ private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mixed.mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mixed.mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
+ startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -713,17 +789,21 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ Transitions.TransitionFinishCallback finishCB = wct -> {
+ mixed.mInFlightSubAnimations--;
+ if (mixed.mInFlightSubAnimations == 0) {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ }
+ };
+
+ mixed.mInFlightSubAnimations++;
boolean consumed = mRecentsHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
if (!consumed) {
+ mixed.mInFlightSubAnimations--;
return false;
}
- //Sync desktop mode state (proto 1)
- if (mDesktopModeController != null) {
- mDesktopModeController.syncSurfaceState(info, finishTransaction);
- return true;
- }
- //Sync desktop mode state (proto 2)
if (mDesktopTasksController != null) {
mDesktopTasksController.syncSurfaceState(info, finishTransaction);
return true;
@@ -805,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
@@ -845,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
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 de03f5826925..9cd318f27355 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
@@ -25,6 +25,7 @@ import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -515,7 +516,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
- addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+ addBackgroundColor(info, backgroundColorForTransition, startTransaction,
finishTransaction);
}
@@ -546,7 +547,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
- private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+ private void addBackgroundColor(@NonNull TransitionInfo info,
@ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
final Color bgColor = Color.valueOf(color);
@@ -558,9 +559,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
.setName("animation-background")
.setCallsite("DefaultTransitionHandler")
.setColorLayer();
-
- mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+
+ // Attaching the background surface to the transition root could unexpectedly make it
+ // cover one of the split root tasks. To avoid this, put the background surface just
+ // above the display area when split is on.
+ final boolean isSplitTaskInvolved =
+ info.getChanges().stream().anyMatch(c-> c.getTaskInfo() != null
+ && c.getTaskInfo().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW);
+ if (isSplitTaskInvolved) {
+ mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+ } else {
+ startTransaction.reparent(backgroundSurface, info.getRootLeash());
+ }
startTransaction.setColor(backgroundSurface, colorArray)
.setLayer(backgroundSurface, -1)
.show(backgroundSurface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
new file mode 100644
index 000000000000..f561aa5568be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+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.util.TransitionUtil;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving the home
+ * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ */
+public class HomeTransitionObserver implements TransitionObserver,
+ RemoteCallable<HomeTransitionObserver> {
+ private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ mListener;
+
+ private @NonNull final Context mContext;
+ private @NonNull final ShellExecutor mMainExecutor;
+ private @NonNull final Transitions mTransitions;
+
+ public HomeTransitionObserver(@NonNull Context context,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Transitions transitions) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mTransitions = transitions;
+
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> mTransitions.registerObserver(this),
+ c -> mTransitions.unregisterObserver(this));
+
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ final int mode = change.getMode();
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
+ && TransitionUtil.isOpenOrCloseMode(mode)) {
+ mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {}
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged,
+ @NonNull IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition,
+ boolean aborted) {}
+
+ /**
+ * Sets the home transition listener that receives any transitions resulting in a change of
+ *
+ */
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ if (listener != null) {
+ mListener.register(listener);
+ } else {
+ mListener.unregister();
+ }
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Invalidates this controller, preventing future calls to send updates.
+ */
+ public void invalidate() {
+ mTransitions.unregisterObserver(this);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index ee55eca31072..18716c68da27 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.bubble
+package com.android.wm.shell.transition;
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ */
+interface IHomeTransitionListener {
+
+ /**
+ * Called when a transition changes the visibility of the home activity.
+ */
+ void onHomeVisibilityChanged(in boolean isVisible);
+}
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) :
- DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index cc4d268a0000..644a6a5114a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -19,6 +19,8 @@ package com.android.wm.shell.transition;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.transition.IHomeTransitionListener;
+
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
*/
@@ -39,4 +41,7 @@ interface IShellTransitions {
* Retrieves the apply-token used by transactions in Shell
*/
IBinder getShellApplyToken() = 3;
+
+ /** Set listener that will receive callbacks about transitions involving home activity */
+ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
}
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 fab2dd2bf3e1..b1fc16ddf19b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for #%d.", mRemote, info.getDebugId());
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -152,6 +152,16 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
}
@Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ try {
+ mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted);
+ } catch (RemoteException e) {
+ Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e);
+ }
+ }
+
+ @Override
public String toString() {
return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
+ mRemote.getRemoteTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index bbf67a6155d7..293b66084d28 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
@@ -86,7 +86,16 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
- mRequestedRemotes.remove(transition);
+ RemoteTransition remoteTransition = mRequestedRemotes.remove(transition);
+ if (remoteTransition == null) {
+ return;
+ }
+
+ try {
+ remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delegating onTransitionConsumed()", e);
+ }
}
@Override
@@ -117,7 +126,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
}
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
info.getDebugId(), pendingRemote);
if (pendingRemote == null) return false;
@@ -137,20 +146,24 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
});
}
};
- Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
+ // 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();
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, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
+ Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
+ if (remoteStartT != startTransaction) {
+ remoteStartT.close();
+ }
+ startTransaction.apply();
unhandleDeath(remote.asBinder(), finishCallback);
mRequestedRemotes.remove(transition);
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
@@ -232,7 +245,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
if (remote == null) return null;
mRequestedRemotes.put(transition, remote);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
- + " for %s: %s", transition, remote);
+ + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index e27e4f990407..5919aad133c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -129,13 +129,12 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* Adds an entry in the trace to log that a request to merge a transition was made.
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
- * @param playingTransitionId The id of the transition we was to merge the transition into.
*/
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
- proto.mergedInto = playingTransitionId;
+ proto.mergeTarget = playingTransitionId;
mTraceBuffer.add(proto);
}
@@ -150,7 +149,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
- proto.mergedInto = playingTransitionId;
+ proto.mergeTarget = playingTransitionId;
mTraceBuffer.add(proto);
}
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 d978eafa97f3..d07d2b7b6db9 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
@@ -346,7 +346,7 @@ public class TransitionAnimationHelper {
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
- .setAllowProtected(true)
+ .setAllowProtected(false)
.setCaptureSecureLayers(true)
.build();
final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
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 c74b3f30e52d..baa9acaafa4b 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
@@ -179,6 +179,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
@@ -188,9 +189,6 @@ public class Transitions implements RemoteCallable<Transitions>,
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
- @Nullable
- private final ShellCommandHandler mShellCommandHandler;
-
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
/** List of {@link Runnable} instances to run when the last active transition has finished. */
@@ -237,7 +235,7 @@ public class Transitions implements RemoteCallable<Transitions>,
@Override
public String toString() {
if (mInfo != null && mInfo.getDebugId() >= 0) {
- return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
+ return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();
}
return mToken.toString() + "@" + getTrack();
}
@@ -275,13 +273,14 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
- this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor, null,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context));
+ this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor,
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
}
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @Nullable ShellCommandHandler shellCommandHandler,
@NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@@ -289,7 +288,6 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @Nullable ShellCommandHandler shellCommandHandler,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
mOrganizer = organizer;
mContext = context;
@@ -300,13 +298,13 @@ public class Transitions implements RemoteCallable<Transitions>,
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellCommandHandler = shellCommandHandler;
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);
- mShellCommandHandler = shellCommandHandler;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
}
@@ -339,9 +337,8 @@ public class Transitions implements RemoteCallable<Transitions>,
TransitionMetrics.getInstance();
}
- if (mShellCommandHandler != null) {
- mShellCommandHandler.addCommandCallback("transitions", this, this);
- }
+ mShellCommandHandler.addCommandCallback("transitions", this, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
public boolean isRegistered() {
@@ -359,7 +356,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(this);
+ return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
}
@Override
@@ -655,8 +652,8 @@ public class Transitions implements RemoteCallable<Transitions>,
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
- transitionToken, info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+ info.getDebugId(), transitionToken, info);
final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
throw new IllegalStateException("Got transitionReady for non-pending transition "
@@ -1073,8 +1070,8 @@ public class Transitions implements RemoteCallable<Transitions>,
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
- transitionToken, request);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+ request.getDebugId(), transitionToken, request);
if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
@@ -1403,9 +1400,12 @@ public class Transitions implements RemoteCallable<Transitions>,
private static class IShellTransitionsImpl extends IShellTransitions.Stub
implements ExternalInterfaceBinder {
private Transitions mTransitions;
+ private final HomeTransitionObserver mHomeTransitionObserver;
- IShellTransitionsImpl(Transitions transitions) {
+ IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
mTransitions = transitions;
+ mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
+ mTransitions);
}
/**
@@ -1413,6 +1413,7 @@ public class Transitions implements RemoteCallable<Transitions>,
*/
@Override
public void invalidate() {
+ mHomeTransitionObserver.invalidate();
mTransitions = null;
}
@@ -1437,6 +1438,14 @@ public class Transitions implements RemoteCallable<Transitions>,
public IBinder getShellApplyToken() {
return SurfaceControl.Transaction.getDefaultApplyToken();
}
+
+ @Override
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
+ (transitions) -> {
+ mHomeTransitionObserver.setHomeTransitionListener(listener);
+ });
+ }
}
private class SettingsObserver extends ContentObserver {
@@ -1475,4 +1484,68 @@ public class Transitions implements RemoteCallable<Transitions>,
pw.println(prefix + "tracing");
mTracer.printShellCommandHelp(pw, prefix + " ");
}
+
+ private void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "Handlers:");
+ for (TransitionHandler handler : mHandlers) {
+ pw.print(innerPrefix);
+ pw.print(handler.getClass().getSimpleName());
+ pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
+ }
+
+ pw.println(prefix + "Observers:");
+ for (TransitionObserver observer : mObservers) {
+ pw.print(innerPrefix);
+ pw.println(observer.getClass().getSimpleName());
+ }
+
+ pw.println(prefix + "Pending Transitions:");
+ for (ActiveTransition transition : mPendingTransitions) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mPendingTransitions.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Ready-during-sync Transitions:");
+ for (ActiveTransition transition : mReadyDuringSync) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mReadyDuringSync.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Tracks:");
+ for (int i = 0; i < mTracks.size(); i++) {
+ final ActiveTransition transition = mTracks.get(i).mActiveTransition;
+ pw.println(innerPrefix + "Track #" + i);
+ pw.print(innerPrefix + "active=");
+ pw.println(transition);
+ if (transition != null) {
+ pw.print(innerPrefix + "hander=");
+ pw.println(transition.mHandler);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 68b5a81f8d7b..b26d0613f3b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -74,9 +75,9 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
Executor executor,
Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransitions = transitions;
mTransactionPool = transactionPool;
mExecutor = executor;
- mTransitions = transitions;
mAnimators.add(splitUnfoldTaskAnimator);
mAnimators.add(fullscreenUnfoldAnimator);
@@ -104,6 +105,16 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
+ if (hasUnfold(info) && transition != mTransition) {
+ // Take over transition that has unfold, we might receive it if no other handler
+ // accepted request in handleRequest, e.g. for rotation + unfold or
+ // TRANSIT_NONE + unfold transitions
+ mTransition = transition;
+
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "UnfoldTransitionHandler: "
+ + "take over startAnimation");
+ }
+
if (transition != mTransition) return false;
for (int i = 0; i < mAnimators.size(); i++) {
@@ -203,6 +214,33 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
&& request.getDisplayChange().isPhysicalDisplayChanged());
}
+ /** Whether `transitionInfo` contains an unfold action. */
+ public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) {
+ // Unfold animation won't play when animations are disabled
+ if (!ValueAnimator.areAnimatorsEnabled()) return false;
+
+ for (int i = 0; i < transitionInfo.getChanges().size(); i++) {
+ final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) != 0) {
+ if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) {
+ continue;
+ }
+
+ // Handle only unfolding, currently we don't have an animation when folding
+ final int afterArea =
+ change.getEndAbsBounds().width() * change.getEndAbsBounds().height();
+ final int beforeArea = change.getStartAbsBounds().width()
+ * change.getStartAbsBounds().height();
+
+ if (afterArea > beforeArea) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
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 f209521b1da4..3e06d2d0e797 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
@@ -19,6 +19,8 @@ import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
+
import java.util.Objects;
/**
@@ -26,6 +28,8 @@ import java.util.Objects;
* tasks/leashes/etc in Launcher
*/
public class SplitBounds implements Parcelable {
+ public static final String KEY_EXTRA_SPLIT_BOUNDS = "key_SplitBounds";
+
public final Rect leftTopBounds;
public final Rect rightBottomBounds;
/** This rect represents the actual gap between the two apps */
@@ -35,6 +39,7 @@ public class SplitBounds implements Parcelable {
public final float leftTaskPercent;
public final float dividerWidthPercent;
public final float dividerHeightPercent;
+ public final @PersistentSnapPosition int snapPosition;
/**
* 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
@@ -45,12 +50,13 @@ public class SplitBounds implements Parcelable {
public final int leftTopTaskId;
public final int rightBottomTaskId;
- public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
- int leftTopTaskId, int rightBottomTaskId) {
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+ int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
this.leftTopTaskId = leftTopTaskId;
this.rightBottomTaskId = rightBottomTaskId;
+ this.snapPosition = snapPosition;
if (rightBottomBounds.top > leftTopBounds.top) {
// vertical apps, horizontal divider
@@ -81,8 +87,9 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = parcel.readBoolean();
leftTopTaskId = parcel.readInt();
rightBottomTaskId = parcel.readInt();
- dividerWidthPercent = parcel.readInt();
- dividerHeightPercent = parcel.readInt();
+ dividerWidthPercent = parcel.readFloat();
+ dividerHeightPercent = parcel.readFloat();
+ snapPosition = parcel.readInt();
}
@Override
@@ -97,6 +104,7 @@ public class SplitBounds implements Parcelable {
parcel.writeInt(rightBottomTaskId);
parcel.writeFloat(dividerWidthPercent);
parcel.writeFloat(dividerHeightPercent);
+ parcel.writeInt(snapPosition);
}
@Override
@@ -127,7 +135,8 @@ public class SplitBounds implements Parcelable {
return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
+ "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n"
+ "Divider: " + visualDividerBounds + "\n"
- + "AppsVertical? " + appsStackedVertically;
+ + "AppsVertical? " + appsStackedVertically + "\n"
+ + "snapPosition: " + snapPosition;
}
public static final Creator<SplitBounds> CREATOR = new Creator<SplitBounds>() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index ce8191067ae9..aff35a347183 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
@@ -64,7 +65,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ taskInfo.getConfiguration());
mHandler = handler;
mChoreographer = choreographer;
@@ -113,7 +115,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
@@ -226,7 +228,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
@Override
- int getCaptionHeightId() {
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 3a0bdd668fbc..716f8b81f158 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,6 +21,7 @@ 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.view.WindowInsets.Type.statusBars;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -50,10 +51,13 @@ import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -64,22 +68,29 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
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.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Supplier;
@@ -93,6 +104,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellController mShellController;
private final Context mContext;
@@ -100,13 +112,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopModeController> mDesktopModeController;
private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final RecentsTransitionHandler mRecentsTransitionHandler;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
- private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl();
+ private final ExclusionRegionListener mExclusionRegionListener =
+ new ExclusionRegionListenerImpl();
private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
new SparseArray<>();
@@ -120,37 +133,46 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private MoveToDesktopAnimator mMoveToDesktopAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
- private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener;
+ private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
+ new DesktopModeKeyguardChangeListener();
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private boolean mInImmersiveMode;
public DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
- Optional<DesktopTasksController> desktopTasksController
+ Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
context,
mainHandler,
mainChoreographer,
shellInit,
+ shellCommandHandler,
taskOrganizer,
displayController,
shellController,
+ displayInsetsController,
syncQueue,
transitions,
- desktopModeController,
desktopTasksController,
+ recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
- new DesktopModeKeyguardChangeListener());
+ rootTaskDisplayAreaOrganizer);
}
@VisibleForTesting
@@ -159,17 +181,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
+ DisplayInsetsController displayInsetsController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -177,21 +201,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskOrganizer = taskOrganizer;
mShellController = shellController;
mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
-
+ mRecentsTransitionHandler = recentsTransitionHandler;
+ mShellCommandHandler = shellCommandHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
- mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
+ mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+ @Override
+ public void onTransitionStarted(IBinder transition) {
+ onRecentsTransitionStarted(transition);
+ }
+ });
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+ new DesktopModeOnInsetsChangedListener());
}
@Override
@@ -207,9 +241,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
if (visible) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.isActive(mContext)
+ if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
}
}
@@ -318,8 +351,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
+ private void onRecentsTransitionStarted(IBinder transition) {
+ // Block relayout on window decorations originating from #onTaskInfoChanges until the
+ // animation completes to avoid interfering with the transition animation.
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ decor.incrementRelayoutBlock();
+ decor.addTransitionPausingRelayout(transition);
+ }
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
- implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+ implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
+ DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -328,6 +372,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final GestureDetector mGestureDetector;
private boolean mIsDragging;
+ private boolean mHasLongClicked;
private boolean mShouldClick;
private int mDragPointerId = -1;
@@ -345,15 +390,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
public void onClick(View v) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (id == R.id.close_window || id == R.id.close_button) {
+ if (id == R.id.close_window) {
mTaskOperations.closeTask(mTaskToken);
- if (mSplitScreenController != null
- && mSplitScreenController.isSplitScreenVisible()) {
- int remainingTaskPosition = mTaskId == mSplitScreenController
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
- .getTaskInfo(remainingTaskPosition);
+ if (isTaskInSplitScreen(mTaskId)) {
+ RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId);
mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
}
} else if (id == R.id.back_button) {
@@ -366,7 +406,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
if (mDesktopTasksController.isPresent()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// App sometimes draws before the insets from WindowDecoration#relayout have
@@ -374,10 +413,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
decoration.incrementRelayoutBlock();
mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+ closeOtherSplitTask(mTaskId);
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.split_screen_button) {
@@ -392,13 +431,34 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
- decoration.closeHandleMenu();
}
} else if (id == R.id.maximize_window) {
+ if (decoration.isMaximizeMenuActive()) {
+ decoration.closeMaximizeMenu();
+ return;
+ }
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
taskInfo, decoration));
decoration.closeHandleMenu();
+ } else if (id == R.id.maximize_menu_maximize_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ } else if (id == R.id.maximize_menu_snap_left_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ } else if (id == R.id.maximize_menu_snap_right_button) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+ mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
+ taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
}
}
@@ -406,17 +466,48 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
- && id != R.id.open_menu_button && id != R.id.close_window) {
+ && id != R.id.open_menu_button && id != R.id.close_window
+ && id != R.id.maximize_window) {
return false;
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+
+ if (!mHasLongClicked) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ decoration.closeMaximizeMenu();
+ }
+
+ final long eventDuration = e.getEventTime() - e.getDownTime();
+ final boolean shouldLongClick = id == R.id.maximize_window && !mIsDragging
+ && !mHasLongClicked && eventDuration >= ViewConfiguration.getLongPressTimeout();
+ if (shouldLongClick) {
+ v.performLongClick();
+ mHasLongClicked = true;
+ return true;
+ }
+
return mDragDetector.onMotionEvent(v, e);
}
+ @Override
+ public boolean onLongClick(View v) {
+ final int id = v.getId();
+ if (id == R.id.maximize_window) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ moveTaskToFront(decoration.mTaskInfo);
+ if (decoration.isMaximizeMenuActive()) {
+ decoration.closeMaximizeMenu();
+ } else {
+ decoration.createMaximizeMenu();
+ }
+ return true;
+ }
+ return false;
+ }
+
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
- mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
}
}
@@ -427,15 +518,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (DesktopModeStatus.isProto2Enabled()
+ if (DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
}
- if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
- == WINDOWING_MODE_FULLSCREEN) {
- return false;
- }
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
@@ -447,11 +533,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
e.getRawY(0));
mIsDragging = false;
mShouldClick = true;
+ mHasLongClicked = false;
return true;
}
case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
@@ -470,7 +558,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
case MotionEvent.ACTION_CANCEL: {
final boolean wasDragging = mIsDragging;
if (!wasDragging) {
- if (mShouldClick && v != null) {
+ if (mShouldClick && v != null && !mHasLongClicked) {
v.performClick();
mShouldClick = false;
return true;
@@ -540,6 +628,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
super.dispose();
}
+ @Override
+ public String toString() {
+ return "EventReceiver"
+ + "{"
+ + "tasksOnDisplay="
+ + mTasksOnDisplay
+ + "}";
+ }
+
private void incrementTaskNumber() {
mTasksOnDisplay++;
}
@@ -585,37 +682,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
- if (DesktopModeStatus.isProto2Enabled()) {
- if (relevantDecor == null
+ if (DesktopModeStatus.isEnabled()) {
+ if (!mInImmersiveMode && (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
- || mTransitionDragActive) {
+ || mTransitionDragActive)) {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
handleEventOutsideFocusedCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
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,
DesktopModeWindowDecoration relevantDecor) {
+ // Returns if event occurs within caption
+ if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
+ return;
+ }
+
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (relevantDecor == null) {
- return;
- }
-
if (!mTransitionDragActive) {
relevantDecor.closeHandleMenuIfNeeded(ev);
+ relevantDecor.closeMaximizeMenuIfNeeded(ev);
}
}
}
@@ -634,7 +729,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
@@ -659,10 +754,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > 2 * statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
mMoveToDesktopAnimator = null;
return;
@@ -692,6 +785,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
if (mMoveToDesktopAnimator == null) {
+ closeOtherSplitTask(relevantDecor.mTaskInfo.taskId);
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface);
@@ -782,7 +876,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
// We can't look at focused task here as only one task will have focus.
- return getSplitScreenDecor(ev);
+ DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
+ return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
} else {
return getFocusedDecor();
}
@@ -853,7 +948,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& taskInfo.isFocused) {
return false;
}
- return DesktopModeStatus.isProto2Enabled()
+ return DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
@@ -880,7 +975,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
taskSurface,
mMainHandler,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mRootTaskDisplayAreaOrganizer);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
@@ -889,8 +985,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
- windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setCornersListener(mCornersListener);
+ windowDecoration.setCaptionListeners(
+ touchEventListener, touchEventListener, touchEventListener);
+ windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
@@ -912,11 +1009,39 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
+ private RunningTaskInfo getOtherSplitTask(int taskId) {
+ @SplitPosition int remainingTaskPosition = mSplitScreenController
+ .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ return mSplitScreenController.getTaskInfo(remainingTaskPosition);
+ }
+
+ private void closeOtherSplitTask(int taskId) {
+ if (isTaskInSplitScreen(taskId)) {
+ mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
+ }
+ }
+
+ private boolean isTaskInSplitScreen(int taskId) {
+ return mSplitScreenController != null
+ && mSplitScreenController.isTaskInSplitScreen(taskId);
+ }
+
+ private void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "DesktopModeWindowDecorViewModel");
+ pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled());
+ pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
+ pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
+ pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
+ }
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
public void onDragStart(int taskId) {
- mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ decoration.closeHandleMenu();
}
}
@@ -926,19 +1051,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private class TaskCornersListenerImpl
- implements DesktopModeWindowDecoration.TaskCornersListener {
+ private class ExclusionRegionListenerImpl
+ implements ExclusionRegionListener {
@Override
- public void onTaskCornersChanged(int taskId, Region corner) {
- mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
- mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
+ public void onExclusionRegionChanged(int taskId, Region region) {
+ mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
}
@Override
- public void onTaskCornersRemoved(int taskId) {
- mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
- mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
+ public void onExclusionRegionDismissed(int taskId) {
+ mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
}
}
@@ -957,6 +1080,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return mIsKeyguardVisible && mIsKeyguardOccluded;
}
}
+
+ @VisibleForTesting
+ class DesktopModeOnInsetsChangedListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ final DesktopModeWindowDecoration decor = getFocusedDecor();
+ if (decor == null) {
+ return;
+ }
+ // If status bar inset is visible, top task is not in immersive mode
+ final boolean inImmersiveMode = !source.isVisible();
+ // Calls WindowDecoration#relayout if decoration visibility needs to be updated
+ if (inImmersiveMode != mInImmersiveMode) {
+ decor.relayout(decor.mTaskInfo);
+ mInImmersiveMode = inImmersiveMode;
+ }
+
+ return;
+ }
+ }
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a359395711e3..eba1a36ef29f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -17,12 +17,19 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.windowingModeToString;
+
+import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import android.app.ActivityManager;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -36,13 +43,17 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.launcher3.icons.BaseIconFactory;
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.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -52,6 +63,7 @@ import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationVi
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -69,6 +81,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
+ private View.OnLongClickListener mOnCaptionLongClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -80,15 +93,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final Point mPositionInParent = new Point();
private HandleMenu mHandleMenu;
+ private MaximizeMenu mMaximizeMenu;
+
private ResizeVeil mResizeVeil;
- private Drawable mAppIcon;
+ private Drawable mAppIconDrawable;
+ private Bitmap mAppIconBitmap;
private CharSequence mAppName;
- private TaskCornersListener mCornersListener;
+ private ExclusionRegionListener mExclusionRegionListener;
private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
private int mRelayoutBlock;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+
+ DesktopModeWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
+ SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
+ }
DesktopModeWindowDecoration(
Context context,
@@ -96,38 +131,40 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+ surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
loadAppInfo();
}
- @Override
- protected Configuration getConfigurationWithOverrides(
- ActivityManager.RunningTaskInfo taskInfo) {
- Configuration configuration = taskInfo.getConfiguration();
- if (DesktopTasksController.isDesktopDensityOverrideSet()) {
- // Density is overridden for desktop tasks. Keep system density for window decoration.
- configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
- }
- return configuration;
- }
-
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
- View.OnTouchListener onCaptionTouchListener) {
+ View.OnTouchListener onCaptionTouchListener,
+ View.OnLongClickListener onLongClickListener) {
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
mOnCaptionTouchListener = onCaptionTouchListener;
+ mOnCaptionLongClickListener = onLongClickListener;
}
- void setCornersListener(TaskCornersListener cornersListener) {
- mCornersListener = cornersListener;
+ void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
+ mExclusionRegionListener = exclusionRegionListener;
}
void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
@@ -148,7 +185,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return;
}
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
@@ -178,9 +215,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
+ mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ // The configuration used to lay out the window decoration. The system context's config is
+ // used when the task density has been overridden to a custom density so that the resources
+ // and views of the decoration aren't affected and match the rest of the System UI, if not
+ // then just use the task's configuration. A copy is made instead of using the original
+ // reference so that the configuration isn't mutated on config changes and diff checks can
+ // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+ // See b/301119301.
+ // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+ // instead of using a whole Configuration as a parameter.
+ final Configuration windowDecorConfig = new Configuration();
+ windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+ ? mContext.getResources().getConfiguration() // Use system context.
+ : mTaskInfo.configuration); // Use task configuration.
+ mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
mRelayoutParams.mCornerRadius =
(int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
@@ -207,8 +258,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
+ mOnCaptionLongClickListener,
mAppName,
- mAppIcon
+ mAppIconBitmap
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
@@ -218,9 +270,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (!mTaskInfo.isFocused) {
closeHandleMenu();
+ closeMaximizeMenu();
}
if (!isDragResizeable) {
+ if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
+ // We still want to track caption bar's exclusion region on a non-resizeable task.
+ updateExclusionRegion();
+ }
closeDragResizeListener();
return;
}
@@ -248,13 +305,58 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final int resize_corner = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_corner);
- // If either task geometry or position have changed, update this task's cornersListener
+ // If either task geometry or position have changed, update this task's
+ // exclusion region listener
if (mDragResizeListener.setGeometry(
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
- mCornersListener.onTaskCornersChanged(mTaskInfo.taskId, getGlobalCornersRegion());
+ updateExclusionRegion();
}
- mPositionInParent.set(mTaskInfo.positionInParent);
+
+ if (isMaximizeMenuActive()) {
+ if (!mTaskInfo.isVisible()) {
+ closeMaximizeMenu();
+ } else {
+ mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT);
+ }
+ }
+ }
+
+ private PointF calculateMaximizeMenuPosition() {
+ final PointF position = new PointF();
+ final Resources resources = mContext.getResources();
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ if (displayLayout == null) return position;
+
+ final int displayWidth = displayLayout.width();
+ final int displayHeight = displayLayout.height();
+ final int captionHeight = getCaptionHeight(mTaskInfo.getWindowingMode());
+
+ final ImageButton maximizeWindowButton =
+ mResult.mRootView.findViewById(R.id.maximize_window);
+ final int[] maximizeButtonLocation = new int[2];
+ maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
+
+ final int menuWidth = loadDimensionPixelSize(
+ resources, R.dimen.desktop_mode_maximize_menu_width);
+ final int menuHeight = loadDimensionPixelSize(
+ resources, R.dimen.desktop_mode_maximize_menu_height);
+
+ float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0]);
+ float menuTop = (mPositionInParent.y + captionHeight);
+ final float menuRight = menuLeft + menuWidth;
+ final float menuBottom = menuTop + menuHeight;
+
+ // If the menu is out of screen bounds, shift it up/left as needed
+ if (menuRight > displayWidth) {
+ menuLeft = (displayWidth - menuWidth);
+ }
+ if (menuBottom > displayHeight) {
+ menuTop = (displayHeight - menuHeight);
+ }
+
+ return new PointF(menuLeft, menuTop);
}
boolean isHandleMenuActive() {
@@ -265,10 +367,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
try {
- IconProvider provider = new IconProvider(mContext);
- mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ final IconProvider provider = new IconProvider(mContext);
+ mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
PackageManager.ComponentInfoFlags.of(0)));
- ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+ final Resources resources = mContext.getResources();
+ final BaseIconFactory factory = new BaseIconFactory(mContext,
+ resources.getDisplayMetrics().densityDpi,
+ resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+ mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+ final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
@@ -289,7 +396,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo,
+ mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo,
mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
}
@@ -335,26 +442,52 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
- * Create and display handle menu window
+ * Create and display maximize menu window
+ */
+ void createMaximizeMenu() {
+ mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
+ mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext,
+ calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
+ mMaximizeMenu.show();
+ }
+
+ /**
+ * Close the maximize menu window
+ */
+ void closeMaximizeMenu() {
+ if (!isMaximizeMenuActive()) return;
+ mMaximizeMenu.close();
+ mMaximizeMenu = null;
+ }
+
+ boolean isMaximizeMenuActive() {
+ return mMaximizeMenu != null;
+ }
+
+ /**
+ * Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
- .setAppIcon(mAppIcon)
+ .setAppIcon(mAppIconBitmap)
.setAppName(mAppName)
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
- .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
+ .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
+ .setCaptionHeight(mResult.mCaptionHeight)
.build();
+ mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
/**
- * Close the handle menu window
+ * Close the handle menu window.
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
+ mWindowDecorViewHolder.onHandleMenuClosed();
mHandleMenu.close();
mHandleMenu = null;
}
@@ -362,6 +495,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void releaseViews() {
closeHandleMenu();
+ closeMaximizeMenu();
super.releaseViews();
}
@@ -387,6 +521,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ /**
+ * Close an open maximize menu if input is outside of menu coordinates
+ *
+ * @param ev the tapped point to compare against
+ */
+ void closeMaximizeMenuIfNeeded(MotionEvent ev) {
+ if (!isMaximizeMenuActive()) return;
+
+ final PointF inputPoint = offsetCaptionLocation(ev);
+ if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ closeMaximizeMenu();
+ }
+ }
+
boolean isFocused() {
return mTaskInfo.isFocused;
}
@@ -426,6 +574,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Returns true if motion event is within the caption's root view's bounds.
+ */
+ boolean checkTouchEventInCaption(MotionEvent ev) {
+ return checkEventInCaptionView(ev, getCaptionViewId());
+ }
+
+ /**
* 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)
@@ -440,6 +595,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
mHandleMenu.checkClickEvent(ev);
+ closeHandleMenuIfNeeded(ev);
}
}
@@ -460,27 +616,43 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
public void close() {
closeDragResizeListener();
closeHandleMenu();
- mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId);
+ mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
super.close();
}
- private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
- if (DesktopModeStatus.isProto1Enabled()) {
- return R.layout.desktop_mode_app_controls_window_decor;
- }
+ private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
}
+ private void updatePositionInParent() {
+ mPositionInParent.set(mTaskInfo.positionInParent);
+ }
+
+ private void updateExclusionRegion() {
+ // An outdated position in parent is one reason for this to be called; update it here.
+ updatePositionInParent();
+ mExclusionRegionListener
+ .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion());
+ }
+
/**
- * Create a new region out of the corner rects of this task.
+ * Create a new exclusion region from the corner rects (if resizeable) and caption bounds
+ * of this task.
*/
- Region getGlobalCornersRegion() {
- Region cornersRegion = mDragResizeListener.getCornersRegion();
- cornersRegion.translate(mPositionInParent.x, mPositionInParent.y);
- return cornersRegion;
+ private Region getGlobalExclusionRegion() {
+ Region exclusionRegion;
+ if (mTaskInfo.isResizeable) {
+ exclusionRegion = mDragResizeListener.getCornersRegion();
+ } else {
+ exclusionRegion = new Region();
+ }
+ exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
+ getCaptionHeight(mTaskInfo.getWindowingMode())));
+ exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
+ return exclusionRegion;
}
/**
@@ -494,8 +666,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
@Override
- int getCaptionHeightId() {
- return R.dimen.freeform_decor_caption_height;
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ ? R.dimen.desktop_mode_fullscreen_decor_caption_height
+ : R.dimen.desktop_mode_freeform_decor_caption_height;
+ }
+
+ private int getCaptionHeight(@WindowingMode int windowingMode) {
+ return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
+ }
+
+ @Override
+ int getCaptionViewId() {
+ return R.id.desktop_mode_caption;
}
/**
@@ -522,6 +705,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mRelayoutBlock++;
}
+ @Override
+ public String toString() {
+ return "{"
+ + "mPositionInParent=" + mPositionInParent + ", "
+ + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ + "taskId=" + mTaskInfo.taskId + ", "
+ + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ + "isFocused=" + isFocused()
+ + "}";
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
@@ -532,25 +726,34 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
SurfaceControl taskSurface,
Handler handler,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ final Configuration windowDecorConfig =
+ DesktopTasksController.isDesktopDensityOverrideSet()
+ ? context.getResources().getConfiguration() // Use system context
+ : taskInfo.configuration; // Use task configuration
return new DesktopModeWindowDecoration(
context,
displayController,
taskOrganizer,
taskInfo,
taskSurface,
+ windowDecorConfig,
handler,
choreographer,
- syncQueue);
+ syncQueue,
+ rootTaskDisplayAreaOrganizer);
}
}
- interface TaskCornersListener {
- /** Inform the implementing class of this task's change in corner resize handles */
- void onTaskCornersChanged(int taskId, Region corner);
+ interface ExclusionRegionListener {
+ /** Inform the implementing class of this task's change in region resize handles */
+ void onExclusionRegionChanged(int taskId, Region region);
- /** Inform the implementing class that this task no longer needs its corners tracked,
- * likely due to it closing. */
- void onTaskCornersRemoved(int taskId);
+ /**
+ * Inform the implementing class that this task no longer needs an exclusion region,
+ * likely due to it closing.
+ */
+ void onExclusionRegionDismissed(int taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index e32bd42acf74..cb0a6c733fe3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -143,6 +143,24 @@ public class DragPositioningCallbackUtility {
}
/**
+ * Calculates the new position of the top edge of the task and returns true if it is below the
+ * disallowed area.
+ *
+ * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
+ * should not finalize the bounds using WCT#setBounds
+ * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
+ * @param repositionStartPoint initial input coordinate
+ * @param y the y position of the motion event
+ * @return true if the top of the task is below the disallowed area
+ */
+ static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
+ Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
+ final float deltaY = y - repositionStartPoint.y;
+ final float topPosition = taskBoundsAtDragStart.top + deltaY;
+ return topPosition > disallowedAreaForEndBoundsHeight;
+ }
+
+ /**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
* the bounds are outside of the stable bounds, they are shifted to place task at the top of the
* stable bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index e0ee25242550..dadd264596fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -45,6 +46,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -78,7 +80,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
@@ -125,7 +130,9 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
}
mTaskOrganizer.applyTransaction(wct);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && y > mDisallowedAreaForEndBoundsHeight) {
+ && DragPositioningCallbackUtility.isBelowDisallowedArea(
+ mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
+ y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ac4a597c15d1..1941d66cc172 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -26,9 +26,12 @@ import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -39,7 +42,6 @@ import android.widget.TextView;
import android.window.SurfaceSyncGroup;
import com.android.wm.shell.R;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
/**
* Handle menu opened when the appropriate button is clicked on.
@@ -53,14 +55,10 @@ class HandleMenu {
private static final String TAG = "HandleMenu";
private final Context mContext;
private final WindowDecoration mParentDecor;
- private WindowDecoration.AdditionalWindow mAppInfoPill;
- private WindowDecoration.AdditionalWindow mWindowingPill;
- private WindowDecoration.AdditionalWindow mMoreActionsPill;
- private final PointF mAppInfoPillPosition = new PointF();
- private final PointF mWindowingPillPosition = new PointF();
- private final PointF mMoreActionsPillPosition = new PointF();
+ private WindowDecoration.AdditionalWindow mHandleMenuWindow;
+ private final PointF mHandleMenuPosition = new PointF();
private final boolean mShouldShowWindowingPill;
- private final Drawable mAppIcon;
+ private final Bitmap mAppIconBitmap;
private final CharSequence mAppName;
private final View.OnClickListener mOnClickListener;
private final View.OnTouchListener mOnTouchListener;
@@ -70,18 +68,16 @@ class HandleMenu {
private final int mCaptionY;
private int mMarginMenuTop;
private int mMarginMenuStart;
- private int mMarginMenuSpacing;
+ private int mMenuHeight;
private int mMenuWidth;
- private int mAppInfoPillHeight;
- private int mWindowingPillHeight;
- private int mMoreActionsPillHeight;
- private int mShadowRadius;
- private int mCornerRadius;
+
+ private final int mCaptionHeight;
HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
+ Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
+ int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
@@ -90,9 +86,10 @@ class HandleMenu {
mCaptionY = captionY;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
- mAppIcon = appIcon;
+ mAppIconBitmap = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
+ mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
}
@@ -101,98 +98,126 @@ class HandleMenu {
final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- createAppInfoPill(t, ssg);
- if (mShouldShowWindowingPill) {
- createWindowingPill(t, ssg);
- }
- createMoreActionsPill(t, ssg);
+ createHandleMenuWindow(t, ssg);
ssg.addTransaction(t);
ssg.markSyncReady();
setupHandleMenu();
+ animateHandleMenu();
}
- private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mAppInfoPillPosition.x;
- final int y = (int) mAppInfoPillPosition.y;
- mAppInfoPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
- "Menu's app info pill",
- t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+ private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
+ final int x = (int) mHandleMenuPosition.x;
+ final int y = (int) mHandleMenuPosition.y;
+ mHandleMenuWindow = mParentDecor.addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+ t, ssg, x, y, mMenuWidth, mMenuHeight);
}
- private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mWindowingPillPosition.x;
- final int y = (int) mWindowingPillPosition.y;
- mWindowingPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
- "Menu's windowing pill",
- t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+ /**
+ * Animates the appearance of the handle menu and its three pills.
+ */
+ private void animateHandleMenu() {
+ final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
+ final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView,
+ mMenuWidth, mCaptionHeight);
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ handleMenuAnimator.animateCaptionHandleExpandToOpen();
+ } else {
+ handleMenuAnimator.animateOpen();
+ }
}
- private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
- final int x = (int) mMoreActionsPillPosition.x;
- final int y = (int) mMoreActionsPillPosition.y;
- mMoreActionsPill = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
- "Menu's more actions pill",
- t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+ /**
+ * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
+ * pill.
+ */
+ private void setupHandleMenu() {
+ final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ handleMenu.setOnTouchListener(mOnTouchListener);
+ setupAppInfoPill(handleMenu);
+ if (mShouldShowWindowingPill) {
+ setupWindowingPill(handleMenu);
+ }
+ setupMoreActionsPill(handleMenu);
}
/**
- * Set up interactive elements and color of this handle menu
+ * Set up interactive elements of handle menu's app info pill.
*/
- private void setupHandleMenu() {
- // App Info pill setup.
- final View appInfoPillView = mAppInfoPill.mWindowViewHost.getView();
- final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
- final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
- final TextView appName = appInfoPillView.findViewById(R.id.application_name);
+ private void setupAppInfoPill(View handleMenu) {
+ final ImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button);
+ final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
+ final TextView appName = handleMenu.findViewById(R.id.application_name);
collapseBtn.setOnClickListener(mOnClickListener);
- appInfoPillView.setOnTouchListener(mOnTouchListener);
- appIcon.setImageDrawable(mAppIcon);
+ appIcon.setImageBitmap(mAppIconBitmap);
appName.setText(mAppName);
+ }
- // Windowing pill setup.
- if (mShouldShowWindowingPill) {
- final View windowingPillView = mWindowingPill.mWindowViewHost.getView();
- final ImageButton fullscreenBtn = windowingPillView.findViewById(
- R.id.fullscreen_button);
- final ImageButton splitscreenBtn = windowingPillView.findViewById(
- R.id.split_screen_button);
- final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
- final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
- fullscreenBtn.setOnClickListener(mOnClickListener);
- splitscreenBtn.setOnClickListener(mOnClickListener);
- floatingBtn.setOnClickListener(mOnClickListener);
- desktopBtn.setOnClickListener(mOnClickListener);
- // The button corresponding to the windowing mode that the task is currently in uses a
- // different color than the others.
- final ColorStateList activeColorStateList = ColorStateList.valueOf(
- mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
- final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
- mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
- fullscreenBtn.setImageTintList(
- mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- ? activeColorStateList : inActiveColorStateList);
- splitscreenBtn.setImageTintList(
- mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- ? activeColorStateList : inActiveColorStateList);
- floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
- ? activeColorStateList : inActiveColorStateList);
- desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? activeColorStateList : inActiveColorStateList);
- }
+ /**
+ * Set up interactive elements and color of handle menu's windowing pill.
+ */
+ private void setupWindowingPill(View handleMenu) {
+ final ImageButton fullscreenBtn = handleMenu.findViewById(
+ R.id.fullscreen_button);
+ final ImageButton splitscreenBtn = handleMenu.findViewById(
+ R.id.split_screen_button);
+ final ImageButton floatingBtn = handleMenu.findViewById(R.id.floating_button);
+ // TODO: Remove once implemented.
+ floatingBtn.setVisibility(View.GONE);
+
+ final ImageButton desktopBtn = handleMenu.findViewById(R.id.desktop_button);
+ fullscreenBtn.setOnClickListener(mOnClickListener);
+ splitscreenBtn.setOnClickListener(mOnClickListener);
+ floatingBtn.setOnClickListener(mOnClickListener);
+ desktopBtn.setOnClickListener(mOnClickListener);
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ final ColorStateList[] iconColors = getWindowingIconColor();
+ final ColorStateList inActiveColorStateList = iconColors[0];
+ final ColorStateList activeColorStateList = iconColors[1];
+ final int windowingMode = mTaskInfo.getWindowingMode();
+ fullscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_FULLSCREEN
+ ? activeColorStateList : inActiveColorStateList);
+ splitscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ ? activeColorStateList : inActiveColorStateList);
+ floatingBtn.setImageTintList(windowingMode == WINDOWING_MODE_PINNED
+ ? activeColorStateList : inActiveColorStateList);
+ desktopBtn.setImageTintList(windowingMode == WINDOWING_MODE_FREEFORM
+ ? activeColorStateList : inActiveColorStateList);
+ }
- // More Actions pill setup.
- final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
- final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
- closeBtn.setOnClickListener(mOnClickListener);
- final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
+ /**
+ * Set up interactive elements & height of handle menu's more actions pill
+ */
+ private void setupMoreActionsPill(View handleMenu) {
+ final Button selectBtn = handleMenu.findViewById(R.id.select_button);
selectBtn.setOnClickListener(mOnClickListener);
+ final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
+ // TODO: Remove once implemented.
+ screenshotBtn.setVisibility(View.GONE);
}
/**
- * Updates the handle menu pills' position variables to reflect their next positions
+ * Returns array of windowing icon color based on current UI theme. First element of the
+ * array is for inactive icons and the second is for active icons.
+ */
+ private ColorStateList[] getWindowingIconColor() {
+ final int mode = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
+ final TypedArray typedArray = mContext.obtainStyledAttributes(new int[]{
+ com.android.internal.R.attr.materialColorOnSurface,
+ com.android.internal.R.attr.materialColorPrimary});
+ final int inActiveColor = typedArray.getColor(0, isNightMode ? Color.WHITE : Color.BLACK);
+ final int activeColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
+ typedArray.recycle();
+ return new ColorStateList[]{ColorStateList.valueOf(inActiveColor),
+ ColorStateList.valueOf(activeColor)};
+ }
+
+ /**
+ * Updates handle menu's position variables to reflect its next position.
*/
private void updateHandleMenuPillPositions() {
final int menuX, menuY;
@@ -209,55 +234,36 @@ class HandleMenu {
menuY = mCaptionY + mMarginMenuStart;
}
- // App Info pill setup.
- final int appInfoPillY = menuY;
- mAppInfoPillPosition.set(menuX, appInfoPillY);
+ // Handle Menu position setup.
+ mHandleMenuPosition.set(menuX, menuY);
- final int windowingPillY, moreActionsPillY;
- if (mShouldShowWindowingPill) {
- windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
- mWindowingPillPosition.set(menuX, windowingPillY);
- moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
- mMoreActionsPillPosition.set(menuX, moreActionsPillY);
- } else {
- // Just start after the end of the app info pill + margins.
- moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
- mMoreActionsPillPosition.set(menuX, moreActionsPillY);
- }
}
/**
* Update pill layout, in case task changes have caused positioning to change.
- * @param t
*/
void relayout(SurfaceControl.Transaction t) {
- if (mAppInfoPill != null) {
+ if (mHandleMenuWindow != null) {
updateHandleMenuPillPositions();
- t.setPosition(mAppInfoPill.mWindowSurface,
- mAppInfoPillPosition.x, mAppInfoPillPosition.y);
- // Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
- final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
- if (shouldShowWindowingPill) {
- t.setPosition(mWindowingPill.mWindowSurface,
- mWindowingPillPosition.x, mWindowingPillPosition.y);
- }
- t.setPosition(mMoreActionsPill.mWindowSurface,
- mMoreActionsPillPosition.x, mMoreActionsPillPosition.y);
+ t.setPosition(mHandleMenuWindow.mWindowSurface,
+ mHandleMenuPosition.x, mHandleMenuPosition.y);
}
}
+
/**
* 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 against.
*/
void checkClickEvent(MotionEvent ev) {
- final View appInfoPill = mAppInfoPill.mWindowViewHost.getView();
- final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
+ final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
+ final ImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
// Translate the input point from display coordinates to the same space as the collapse
// button, meaning its parent (app info pill view).
- final PointF inputPoint = new PointF(ev.getX() - mAppInfoPillPosition.x,
- ev.getY() - mAppInfoPillPosition.y);
+ final PointF inputPoint = new PointF(ev.getX() - mHandleMenuPosition.x,
+ ev.getY() - mHandleMenuPosition.y);
if (pointInView(collapse, inputPoint.x, inputPoint.y)) {
mOnClickListener.onClick(collapse);
}
@@ -267,27 +273,15 @@ class HandleMenu {
* A valid menu input is one of the following:
* An input that happens in the menu views.
* Any input before the views have been laid out.
+ *
* @param inputPoint the input to compare against.
*/
boolean isValidMenuInput(PointF inputPoint) {
if (!viewsLaidOut()) return true;
- final boolean pointInAppInfoPill = pointInView(
- mAppInfoPill.mWindowViewHost.getView(),
- inputPoint.x - mAppInfoPillPosition.x,
- inputPoint.y - mAppInfoPillPosition.y);
- boolean pointInWindowingPill = false;
- if (mWindowingPill != null) {
- pointInWindowingPill = pointInView(
- mWindowingPill.mWindowViewHost.getView(),
- inputPoint.x - mWindowingPillPosition.x,
- inputPoint.y - mWindowingPillPosition.y);
- }
- final boolean pointInMoreActionsPill = pointInView(
- mMoreActionsPill.mWindowViewHost.getView(),
- inputPoint.x - mMoreActionsPillPosition.x,
- inputPoint.y - mMoreActionsPillPosition.y);
-
- return pointInAppInfoPill || pointInWindowingPill || pointInMoreActionsPill;
+ return pointInView(
+ mHandleMenuWindow.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuPosition.x,
+ inputPoint.y - mHandleMenuPosition.y);
}
private boolean pointInView(View v, float x, float y) {
@@ -297,33 +291,33 @@ class HandleMenu {
/**
* Check if the views for handle menu can be seen.
- * @return
*/
private boolean viewsLaidOut() {
- return mAppInfoPill.mWindowViewHost.getView().isLaidOut();
+ return mHandleMenuWindow.mWindowViewHost.getView().isLaidOut();
}
-
private void loadHandleMenuDimensions() {
final Resources resources = mContext.getResources();
mMenuWidth = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_width);
+ mMenuHeight = getHandleMenuHeight(resources);
mMarginMenuTop = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_margin_top);
mMarginMenuStart = loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_margin_start);
- mMarginMenuSpacing = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
- mAppInfoPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_app_info_pill_height);
- mWindowingPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height);
- mMoreActionsPillHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
- mShadowRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_shadow_radius);
- mCornerRadius = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_corner_radius);
+ }
+
+ /**
+ * Determines handle menu height based on if windowing pill should be shown.
+ */
+ private int getHandleMenuHeight(Resources resources) {
+ int menuHeight = loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_height);
+ if (!mShouldShowWindowingPill) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height);
+ }
+ return menuHeight;
}
private int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -334,26 +328,21 @@ class HandleMenu {
}
void close() {
- mAppInfoPill.releaseView();
- mAppInfoPill = null;
- if (mWindowingPill != null) {
- mWindowingPill.releaseView();
- mWindowingPill = null;
- }
- mMoreActionsPill.releaseView();
- mMoreActionsPill = null;
+ mHandleMenuWindow.releaseView();
+ mHandleMenuWindow = null;
}
static final class Builder {
private final WindowDecoration mParent;
private CharSequence mName;
- private Drawable mAppIcon;
+ private Bitmap mAppIcon;
private View.OnClickListener mOnClickListener;
private View.OnTouchListener mOnTouchListener;
private int mLayoutId;
private int mCaptionX;
private int mCaptionY;
private boolean mShowWindowingPill;
+ private int mCaptionHeight;
Builder(@NonNull WindowDecoration parent) {
@@ -365,7 +354,7 @@ class HandleMenu {
return this;
}
- Builder setAppIcon(@Nullable Drawable appIcon) {
+ Builder setAppIcon(@Nullable Bitmap appIcon) {
mAppIcon = appIcon;
return this;
}
@@ -396,9 +385,14 @@ class HandleMenu {
return this;
}
+ Builder setCaptionHeight(int captionHeight) {
+ mCaptionHeight = captionHeight;
+ return this;
+ }
+
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
+ mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
new file mode 100644
index 000000000000..531de1f79ea8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
+import androidx.core.view.children
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
+
+/** Animates the Handle Menu opening. */
+class HandleMenuAnimator(
+ private val handleMenu: View,
+ private val menuWidth: Int,
+ private val captionHeight: Float
+) {
+ companion object {
+ private const val MENU_Y_TRANSLATION_DURATION: Long = 150
+ private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150
+ private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217
+ private const val HEADER_ELEVATION_DURATION: Long = 83
+ private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100
+ private const val BODY_SCALE_DURATION: Long = 180
+ private const val BODY_ALPHA_DURATION: Long = 150
+ private const val BODY_ELEVATION_DURATION: Long = 83
+ private const val BODY_CONTENT_ALPHA_DURATION: Long = 167
+
+ private const val ELEVATION_DELAY: Long = 33
+ private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67
+ private const val BODY_SCALE_DELAY: Long = 50
+ private const val BODY_ALPHA_DELAY: Long = 133
+
+ private const val HALF_INITIAL_SCALE: Float = 0.5f
+ private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
+ private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f
+ }
+
+ private val animators: MutableList<Animator> = mutableListOf()
+
+ private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
+ private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
+ private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+
+ /** Animates the opening of the handle menu. */
+ fun animateOpen() {
+ prepareMenuForAnimation()
+ appInfoPillExpand()
+ animateAppInfoPill()
+ animateWindowingPill()
+ animateMoreActionsPill()
+ runAnimations()
+ }
+
+ /**
+ * Animates the opening of the handle menu. The caption handle in full screen and split screen
+ * will expand until it assumes the shape of the app info pill. Then, the other two pills will
+ * appear.
+ */
+ fun animateCaptionHandleExpandToOpen() {
+ prepareMenuForAnimation()
+ captionHandleExpandIntoAppInfoPill()
+ animateAppInfoPill()
+ animateWindowingPill()
+ animateMoreActionsPill()
+ runAnimations()
+ }
+
+ /**
+ * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
+ * Presets pivots of handle menu and body pills for scaling animation.
+ */
+ private fun prepareMenuForAnimation() {
+ // Preset opacity
+ appInfoPill.children.forEach { it.alpha = 0f }
+ windowingPill.alpha = 0f
+ moreActionsPill.alpha = 0f
+
+ // Setup pivots.
+ handleMenu.pivotX = menuWidth / 2f
+ handleMenu.pivotY = 0f
+
+ windowingPill.pivotX = menuWidth / 2f
+ windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ moreActionsPill.pivotX = menuWidth / 2f
+ moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+ }
+
+ private fun animateAppInfoPill() {
+ // Header Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_DELAY
+ duration = HEADER_ELEVATION_DURATION
+ }
+
+ // Content Opacity Animation
+ appInfoPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = HEADER_CONTENT_ALPHA_DELAY
+ duration = HEADER_CONTENT_ALPHA_DURATION
+ }
+ }
+ }
+
+ private fun captionHandleExpandIntoAppInfoPill() {
+ // Header scaling animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
+ .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
+ .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
+
+ // Downward y-translation animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
+ duration = MENU_Y_TRANSLATION_DURATION
+ }
+ }
+
+ private fun appInfoPillExpand() {
+ // Header scaling animation
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ duration = HEADER_FREEFORM_SCALE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ duration = HEADER_FREEFORM_SCALE_DURATION
+ }
+ }
+
+ private fun animateWindowingPill() {
+ // Windowing X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_DELAY
+ duration = BODY_SCALE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_DELAY
+ duration = BODY_SCALE_DURATION
+ }
+
+ // Windowing Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_DELAY
+ duration = BODY_ALPHA_DURATION
+ }
+
+ // Windowing Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_DELAY
+ duration = BODY_ELEVATION_DURATION
+ }
+
+ // Windowing Content Opacity Animation
+ windowingPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_DELAY
+ duration = BODY_CONTENT_ALPHA_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateMoreActionsPill() {
+ // More Actions X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_DELAY
+ duration = BODY_SCALE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_DELAY
+ duration = BODY_SCALE_DURATION
+ }
+
+ // More Actions Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_DELAY
+ duration = BODY_ALPHA_DURATION
+ }
+
+ // More Actions Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_DELAY
+ duration = BODY_ELEVATION_DURATION
+ }
+
+ // More Actions Content Opacity Animation
+ moreActionsPill.children.forEach {
+ animators +=
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_DELAY
+ duration = BODY_CONTENT_ALPHA_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ /** Runs the list of animators concurrently. */
+ private fun runAnimations() {
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(animators)
+ animatorSet.start()
+ animators.clear()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
new file mode 100644
index 000000000000..921708faab16
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PixelFormat
+import android.graphics.PointF
+import android.view.LayoutInflater
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import android.widget.Button
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
+import java.util.function.Supplier
+
+
+/**
+ * Menu that appears when user long clicks the maximize button. Gives the user the option to
+ * maximize the task or snap the task to the right or left half of the screen.
+ */
+class MaximizeMenu(
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val displayController: DisplayController,
+ private val taskInfo: RunningTaskInfo,
+ private val onClickListener: OnClickListener,
+ private val decorWindowContext: Context,
+ private val menuPosition: PointF,
+ private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
+) {
+ private var maximizeMenu: AdditionalWindow? = null
+ private lateinit var viewHost: SurfaceControlViewHost
+ private lateinit var leash: SurfaceControl
+ private val shadowRadius = loadDimensionPixelSize(
+ R.dimen.desktop_mode_maximize_menu_shadow_radius
+ ).toFloat()
+ private val cornerRadius = loadDimensionPixelSize(
+ R.dimen.desktop_mode_maximize_menu_corner_radius
+ ).toFloat()
+
+ /** Position the menu relative to the caption's position. */
+ fun positionMenu(position: PointF, t: Transaction) {
+ menuPosition.set(position)
+ t.setPosition(leash, menuPosition.x, menuPosition.y)
+ }
+
+ /** Creates and shows the maximize window. */
+ fun show() {
+ if (maximizeMenu != null) return
+ createMaximizeMenu()
+ setupMaximizeMenu()
+ }
+
+ /** Closes the maximize window and releases its view. */
+ fun close() {
+ maximizeMenu?.releaseView()
+ maximizeMenu = null
+ }
+
+ /** Create a maximize menu that is attached to the display area. */
+ private fun createMaximizeMenu() {
+ val t = transactionSupplier.get()
+ val v = LayoutInflater.from(decorWindowContext).inflate(
+ R.layout.desktop_mode_window_decor_maximize_menu,
+ null // Root
+ )
+ val builder = SurfaceControl.Builder()
+ rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder)
+ leash = builder
+ .setName("Maximize Menu")
+ .setContainerLayer()
+ .build()
+ val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
+ val lp = WindowManager.LayoutParams(
+ menuWidth,
+ menuHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT
+ )
+ lp.title = "Maximize Menu for Task=" + taskInfo.taskId
+ lp.setTrustedOverlay()
+ val windowManager = WindowlessWindowManager(
+ taskInfo.configuration,
+ leash,
+ null // HostInputToken
+ )
+ viewHost = SurfaceControlViewHost(decorWindowContext,
+ displayController.getDisplay(taskInfo.displayId), windowManager,
+ "MaximizeMenu")
+ viewHost.setView(v, lp)
+
+ // Bring menu to front when open
+ t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
+ .setPosition(leash, menuPosition.x, menuPosition.y)
+ .setWindowCrop(leash, menuWidth, menuHeight)
+ .setShadowRadius(leash, shadowRadius)
+ .setCornerRadius(leash, cornerRadius)
+ .show(leash)
+ maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
+
+ syncQueue.runInSync { transaction ->
+ transaction.merge(t)
+ t.close()
+ }
+ }
+
+ private fun loadDimensionPixelSize(resourceId: Int): Int {
+ return if (resourceId == Resources.ID_NULL) {
+ 0
+ } else {
+ decorWindowContext.resources.getDimensionPixelSize(resourceId)
+ }
+ }
+
+ private fun setupMaximizeMenu() {
+ val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
+
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_maximize_button
+ ).setOnClickListener(onClickListener)
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_snap_right_button
+ ).setOnClickListener(onClickListener)
+ maximizeMenuView.requireViewById<Button>(
+ R.id.maximize_menu_snap_left_button
+ ).setOnClickListener(onClickListener)
+ }
+
+ /**
+ * A valid menu input is one of the following:
+ * An input that happens in the menu views.
+ * Any input before the views have been laid out.
+ *
+ * @param inputPoint the input to compare against.
+ */
+ fun isValidMenuInput(inputPoint: PointF): Boolean {
+ val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
+ return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
+ inputPoint.y - menuPosition.y)
+ }
+
+ private fun pointInView(v: View, x: Float, y: Float): Boolean {
+ return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ /**
+ * Check if the views for maximize menu can be seen.
+ */
+ private fun viewsLaidOut(): Boolean {
+ return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index bfce72bcadf0..368231e2d7f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -49,11 +49,13 @@ public class ResizeVeil {
private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final Drawable mAppIcon;
+ private ImageView mIconView;
private SurfaceControl mParentSurface;
private SurfaceControl mVeilSurface;
private final RunningTaskInfo mTaskInfo;
private SurfaceControlViewHost mViewHost;
private final Display mDisplay;
+ private ValueAnimator mVeilAnimator;
public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
@@ -97,8 +99,8 @@ public class ResizeVeil {
mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil");
mViewHost.setView(v, lp);
- final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon);
- appIcon.setImageDrawable(mAppIcon);
+ mIconView = mViewHost.getView().findViewById(R.id.veil_application_icon);
+ mIconView.setImageDrawable(mAppIcon);
}
/**
@@ -123,17 +125,34 @@ public class ResizeVeil {
relayout(taskBounds, t);
if (fadeIn) {
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(0f, 1f);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
- t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+ mVeilAnimator = new ValueAnimator();
+ mVeilAnimator.setFloatValues(0f, 1f);
+ mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ mVeilAnimator.addUpdateListener(animation -> {
+ t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction());
t.apply();
});
+ mVeilAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ t.setAlpha(mVeilSurface, 1);
+ t.apply();
+ }
+ });
+
+ final ValueAnimator iconAnimator = new ValueAnimator();
+ iconAnimator.setFloatValues(0f, 1f);
+ iconAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ iconAnimator.addUpdateListener(animation -> {
+ mIconView.setAlpha(animation.getAnimatedFraction());
+ });
t.show(mVeilSurface)
.addTransactionCommittedListener(
- mContext.getMainExecutor(), () -> animator.start())
+ mContext.getMainExecutor(), () -> {
+ mVeilAnimator.start();
+ iconAnimator.start();
+ })
.setAlpha(mVeilSurface, 0);
} else {
// Show the veil immediately at full opacity.
@@ -172,11 +191,17 @@ public class ResizeVeil {
/**
* Calls relayout to update task and veil bounds.
+ * Finishes veil fade in if animation is currently running; this is to prevent empty space
+ * being visible behind the transparent veil during a fast resize.
*
* @param t a transaction to be applied in sync with the veil draw.
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+ if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
+ mVeilAnimator.removeAllUpdateListeners();
+ mVeilAnimator.end();
+ }
relayout(newBounds, t);
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c9c58de6e82a..852c037baad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -98,7 +100,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mDesktopWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
@@ -142,7 +147,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
}
- } else if (y > mDisallowedAreaForEndBoundsHeight) {
+ } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
+ mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
+ y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
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 0b0d9d5086f4..634b7558c7d8 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
@@ -17,8 +17,12 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.statusBars;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -28,6 +32,8 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.view.Display;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -42,6 +48,7 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import java.util.function.Supplier;
@@ -115,6 +122,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mCaptionContainerSurface;
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
+ private Configuration mWindowDecorConfig;
+ private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
@@ -125,10 +134,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface) {
- this(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ SurfaceControl taskSurface,
+ Configuration windowDecorConfig) {
+ this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -136,33 +147,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
+ @NonNull SurfaceControl taskSurface,
+ Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
- mTaskSurface = taskSurface;
+ mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(
- getConfigurationWithOverrides(mTaskInfo));
- }
-
- /**
- * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
- *
- * Allows values to be overridden before returning the configuration.
- */
- protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
- return taskInfo.getConfiguration();
+ mWindowDecorConfig = windowDecorConfig;
+ mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
}
/**
@@ -179,7 +183,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
RelayoutResult<T> outResult) {
outResult.reset();
- final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
@@ -198,18 +201,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
- if (oldTaskConfig.densityDpi != taskConfig.densityDpi
+
+ final int oldDensityDpi = mWindowDecorConfig.densityDpi;
+ final int oldNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig
+ : mTaskInfo.getConfiguration();
+ final int newDensityDpi = mWindowDecorConfig.densityDpi;
+ final int newNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ if (oldDensityDpi != newDensityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId
- || oldLayoutResId != mLayoutResId) {
+ || oldLayoutResId != mLayoutResId
+ || oldNightMode != newNightMode) {
releaseViews();
if (!obtainDisplayOrRegisterListener()) {
outResult.mRootView = null;
return;
}
- mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
+ mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
+ mDecorWindowContext.setTheme(mContext.getThemeResId());
if (params.mLayoutResId != 0) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
.inflate(params.mLayoutResId, null);
@@ -221,8 +232,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.inflate(params.mLayoutResId, null);
}
+ updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
final Resources resources = mDecorWindowContext.getResources();
+ final Configuration taskConfig = mTaskInfo.getConfiguration();
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN;
outResult.mWidth = taskBounds.width();
outResult.mHeight = taskBounds.height();
@@ -253,10 +269,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -265,33 +281,60 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
- wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ if (mIsCaptionVisible) {
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+ mCaptionInsetsRect);
+ } else {
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.captionBar());
+ wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
+ WindowInsets.Type.mandatorySystemGestures());
+ }
} else {
startT.hide(mCaptionContainerSurface);
}
// Task surface itself
- float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
- int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
- mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
- mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
- .setShadowRadius(mTaskSurface, shadowRadius)
- .setColor(mTaskSurface, mTmpColor)
+ if (isFullscreen) {
+ // Setting the task crop to the width/height stops input events from being sent to
+ // some regions of the app window. See b/300324920
+ // TODO(b/296921174): investigate whether crop/position needs to be set by window
+ // decorations at all when transition handlers are already taking ownership of the task
+ // surface placement/crop, especially when in fullscreen where tasks cannot be
+ // drag-resized by the window decoration.
+ startT.setWindowCrop(mTaskSurface, null);
+ finishT.setWindowCrop(mTaskSurface, null);
+ // Shadow is not needed for fullscreen tasks
+ shadowRadius = 0;
+ } else {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+ }
+ startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius)
- .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ .setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ // When fluid resize is enabled, add a background to freeform tasks
+ 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.setColor(mTaskSurface, mTmpColor);
+ }
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ startT.unsetColor(mTaskSurface);
}
if (mCaptionWindowManager == null) {
@@ -305,7 +348,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, captionHeight,
+ new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -325,7 +368,38 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
- int getCaptionHeightId() {
+ /**
+ * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
+ */
+ private void updateCaptionVisibility(View rootView, int displayId) {
+ final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
+ for (int i = 0; i < insetsState.sourceSize(); i++) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() != statusBars()) {
+ continue;
+ }
+
+ mIsCaptionVisible = source.isVisible();
+ setCaptionVisibility(rootView, mIsCaptionVisible);
+
+ return;
+ }
+ }
+
+ private void setCaptionVisibility(View rootView, boolean visible) {
+ if (rootView == null) {
+ return;
+ }
+ final int v = visible ? View.VISIBLE : View.GONE;
+ final View captionView = rootView.findViewById(getCaptionViewId());
+ captionView.setVisibility(v);
+ }
+
+ int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return Resources.ID_NULL;
+ }
+
+ int getCaptionViewId() {
return Resources.ID_NULL;
}
@@ -382,6 +456,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
+ mTaskSurface.release();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -398,6 +473,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return resources.getDimension(resourceId);
}
+ private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+ Supplier<SurfaceControl> surfaceControlSupplier) {
+ final SurfaceControl copy = surfaceControlSupplier.get();
+ copy.copyFrom(sc, "WindowDecoration");
+ return copy;
+ }
+
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
@@ -408,13 +490,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
- * @param shadowRadius radius of the shadow of the new window
- * @param cornerRadius radius of the corners of the new window
* @return the {@link AdditionalWindow} that was added.
*/
AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
- SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
- int cornerRadius) {
+ SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -425,8 +504,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
- .setShadowRadius(windowSurfaceControl, shadowRadius)
- .setCornerRadius(windowSurfaceControl, cornerRadius)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
@@ -447,8 +524,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* Adds caption inset source to a WCT
*/
public void addCaptionInset(WindowContainerTransaction wct) {
- final int captionHeightId = getCaptionHeightId();
- if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+ final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
+ if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL
+ || !mIsCaptionVisible) {
return;
}
@@ -470,6 +548,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mCaptionX;
int mCaptionY;
+ Configuration mWindowDecorConfig;
+
boolean mApplyStartTransactionOnDraw;
void reset() {
@@ -484,10 +564,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionY = 0;
mApplyStartTransactionOnDraw = false;
+ mWindowDecorConfig = null;
}
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mCaptionHeight;
int mWidth;
int mHeight;
T mRootView;
@@ -495,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
void reset() {
mWidth = 0;
mHeight = 0;
+ mCaptionHeight = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index a9eb8829bd2c..b739ad3793e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -2,9 +2,10 @@ package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
+import android.graphics.Bitmap
import android.graphics.drawable.GradientDrawable
import android.view.View
+import android.view.View.OnLongClickListener
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
@@ -19,8 +20,9 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
rootView: View,
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: View.OnClickListener,
+ onLongClickListener: OnLongClickListener,
appName: CharSequence,
- appIcon: Drawable
+ appIconBitmap: Bitmap
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -39,13 +41,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
openMenuButton.setOnTouchListener(onCaptionTouchListener)
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ maximizeWindowButton.setOnTouchListener(onCaptionTouchListener)
+ maximizeWindowButton.onLongClickListener = onLongClickListener
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
- appIconImageView.setImageDrawable(appIcon)
+ appIconImageView.setImageBitmap(appIconBitmap)
}
override fun bindData(taskInfo: RunningTaskInfo) {
-
val captionDrawable = captionView.background as GradientDrawable
taskInfo.taskDescription?.statusBarColor?.let {
captionDrawable.setColor(it)
@@ -60,6 +63,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
}
+ override fun onHandleMenuOpened() {}
+
+ override fun onHandleMenuClosed() {}
+
private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_app_name_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 9374ac95e83d..9dc86db4f59b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -1,11 +1,13 @@
package com.android.wm.shell.windowdecor.viewholder
+import android.animation.ObjectAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.widget.ImageButton
import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
@@ -17,6 +19,10 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
onCaptionButtonClickListener: View.OnClickListener
) : DesktopModeWindowDecorationViewHolder(rootView) {
+ companion object {
+ private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
+ }
+
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
@@ -35,6 +41,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
+ override fun onHandleMenuOpened() {
+ animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
+ }
+
+ override fun onHandleMenuClosed() {
+ animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
+ }
+
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
@@ -42,4 +56,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
}
}
+
+ /** Animate appearance/disappearance of caption handle as the handle menu is animated. */
+ private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
+ val animator =
+ ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
+ duration = CAPTION_HANDLE_ANIMATION_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ animator.start()
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 49e8d15dcc02..8b405f02ef29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -35,4 +35,10 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
}
} ?: false
}
+
+ /** Callback when the handle menu is opened. */
+ abstract fun onHandleMenuOpened()
+
+ /** Callback when the handle menu is closed. */
+ abstract fun onHandleMenuClosed()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b062fbd510ca..366f7b1e678f 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -28,36 +28,6 @@ filegroup {
srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"],
}
-filegroup {
- name: "WMShellFlickerTestsBase-src",
- srcs: ["src/com/android/wm/shell/flicker/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsBubbles-src",
- srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip-src",
- srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
-}
-
-filegroup {
- name: "WMShellFlickerTestsSplitScreen-src",
- srcs: [
- "src/com/android/wm/shell/flicker/splitscreen/*.kt",
- "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerServiceTests-src",
- srcs: [
- "src/com/android/wm/shell/flicker/service/**/*.kt",
- ],
-}
-
java_library {
name: "wm-shell-flicker-utils",
platform_apis: true,
@@ -77,13 +47,12 @@ java_library {
"wm-flicker-common-assertions",
"launcher-helper-lib",
"launcher-aosp-tapl",
+ "com_android_wm_shell_flags_lib",
],
}
java_defaults {
- name: "WMShellFlickerTestsDefault",
- manifest: "manifests/AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
+ name: "WMShellFlickerTestsDefaultWithoutTemplate",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -97,6 +66,7 @@ java_defaults {
"flickertestapplib",
"flickerlib",
"flickerlib-helpers",
+ "flickerlib-trace_processor_shell",
"platform-test-annotations",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
@@ -109,68 +79,14 @@ java_defaults {
],
}
-android_test {
- name: "WMShellFlickerTestsOther",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestOther.xml"],
- package_name: "com.android.wm.shell.flicker",
- instrumentation_target_package: "com.android.wm.shell.flicker",
- srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- ],
- exclude_srcs: [
- ":WMShellFlickerTestsBubbles-src",
- ":WMShellFlickerTestsPip-src",
- ":WMShellFlickerTestsSplitScreen-src",
- ":WMShellFlickerServiceTests-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsBubbles",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
- package_name: "com.android.wm.shell.flicker.bubbles",
- instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsBubbles-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestPip.xml"],
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsPip-src",
- ],
-}
-
-android_test {
- name: "WMShellFlickerTestsSplitScreen",
- defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
- package_name: "com.android.wm.shell.flicker.splitscreen",
- instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsSplitScreen-src",
- ],
+java_defaults {
+ name: "WMShellFlickerTestsDefault",
+ defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
+ test_config_template: "AndroidTestTemplate.xml",
}
-android_test {
- name: "WMShellFlickerServiceTests",
+java_library {
+ name: "WMShellFlickerTestsBase",
defaults: ["WMShellFlickerTestsDefault"],
- additional_manifests: ["manifests/AndroidManifestService.xml"],
- package_name: "com.android.wm.shell.flicker.service",
- instrumentation_target_package: "com.android.wm.shell.flicker.service",
- srcs: [
- ":WMShellFlickerTestsBase-src",
- ":WMShellFlickerServiceTests-src",
- ],
+ srcs: ["src/com/android/wm/shell/flicker/*.kt"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index c8a96377800f..b00d88e61e13 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -38,6 +38,9 @@
<!-- Increase trace size: 20mb for WM and 80mb for SF -->
<option name="run-command" value="cmd window tracing size 20480"/>
<option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ <!-- b/307664397 - Ensure camera has the correct permissions and doesn't show a dialog -->
+ <option name="run-command"
+ value="pm grant com.google.android.GoogleCamera android.permission.ACCESS_FINE_LOCATION"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
@@ -57,6 +60,14 @@
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk"/>
</target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -90,11 +101,11 @@
<option name="directory-keys"
value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
<option name="directory-keys"
- value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
new file mode 100644
index 000000000000..bae701f2cbeb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsAppCompat-src",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsOther",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker",
+ instrumentation_target_package: "com.android.wm.shell.flicker",
+ srcs: [":WMShellFlickerTestsAppCompat-src"],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml
index 6a87de47def4..2af1e74fb3d5 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidManifest.xml
@@ -1,18 +1,18 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
@@ -45,9 +45,13 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<!-- Enable bubble notification-->
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
- <!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
@@ -65,4 +69,9 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..1df11369a049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml
index cf642f63a41d..4bd9ca049f55 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/res/xml/network_security_config.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2023 The Android Open Source Project
~
@@ -14,11 +15,8 @@
~ limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 36acb58d6139..adf92d8854ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -28,7 +28,6 @@ import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.utils.appWindowKeepVisible
import com.android.wm.shell.flicker.utils.layerKeepVisible
-
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -37,9 +36,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- @JvmField
- @Rule
- val letterboxRule: LetterboxRule = LetterboxRule()
+ @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -53,7 +50,7 @@ abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
}
@Before
- fun before() {
+ fun setUp() {
Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
index 5a1136f97c6f..744e8c2eb06f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -23,16 +23,14 @@ import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
-/**
- * JUnit Rule to handle letterboxStyles and states
- */
+/** JUnit Rule to handle letterboxStyles and states */
class LetterboxRule(
- private val withLetterboxEducationEnabled: Boolean = false,
- private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
- private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
) : TestRule {
- private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
private lateinit var _letterboxStyle: MutableMap<String, String>
val letterboxStyle: Map<String, String>
@@ -62,8 +60,7 @@ class LetterboxRule(
var hasLetterboxEducationStateChanged = false
if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
hasLetterboxEducationStateChanged = true
- execAdb("wm set-letterbox-style --isEducationEnabled " +
- withLetterboxEducationEnabled)
+ execAdb("wm set-letterbox-style --isEducationEnabled " + withLetterboxEducationEnabled)
}
return try {
object : Statement() {
@@ -74,8 +71,8 @@ class LetterboxRule(
}
} finally {
if (hasLetterboxEducationStateChanged) {
- execAdb("wm set-letterbox-style --isEducationEnabled " +
- isLetterboxEducationEnabled
+ execAdb(
+ "wm set-letterbox-style --isEducationEnabled " + isLetterboxEducationEnabled
)
}
resetLetterboxStyle()
@@ -100,9 +97,10 @@ class LetterboxRule(
execAdb("wm reset-letterbox-style")
}
- private fun asInt(str: String?): Int? = try {
- str?.toInt()
- } catch (e: NumberFormatException) {
- null
- }
-} \ No newline at end of file
+ private fun asInt(str: String?): Int? =
+ try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 67d5718e6c1f..1e5e42fb077e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -66,7 +66,8 @@ class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(fl
*/
@Postsubmit
@Test
- fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) }
+ fun letterboxAppFocusedAtEnd() =
+ flicker.assertEventLog { focusChanges(letterboxApp.packageName) }
@Postsubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index e6ca261a317f..2fa1ec386781 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -54,9 +54,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
- setup {
- letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
- }
+ setup { letterboxTranslucentLauncherApp.launchViaIntent(wmHelper) }
transitions {
waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
}
@@ -66,9 +64,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
}
}
- /**
- * Checks the transparent activity is launched on top of the opaque one
- */
+ /** Checks the transparent activity is launched on top of the opaque one */
@Postsubmit
@Test
fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
@@ -79,18 +75,14 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
}
}
- /**
- * Checks that the activity is letterboxed
- */
+ /** Checks that the activity is letterboxed */
@Postsubmit
@Test
fun translucentActivityIsLetterboxed() {
flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
}
- /**
- * Checks that the translucent activity inherits bounds from the opaque one.
- */
+ /** Checks that the translucent activity inherits bounds from the opaque one. */
@Postsubmit
@Test
fun translucentActivityInheritsBoundsFromOpaqueActivity() {
@@ -100,29 +92,26 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
}
}
- /**
- * Checks that the translucent activity has rounded corners
- */
+ /** Checks that the translucent activity has rounded corners */
@Postsubmit
@Test
fun translucentActivityHasRoundedCorners() {
- flicker.assertLayersEnd {
- this.hasRoundedCorners(letterboxTranslucentApp)
- }
+ flicker.assertLayersEnd { this.hasRoundedCorners(letterboxTranslucentApp) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
- * navigation modes.
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+ * modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory
- .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index d3f3c5b7c672..b74aa1d7bf73 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -49,8 +49,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
- BaseAppCompat(flicker) {
+class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index 68fa8d2fc2e8..68fa8d2fc2e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index fcb6931af9a2..fcb6931af9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
new file mode 100644
index 000000000000..446aad8a8936
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.os.Build
+import android.platform.test.annotations.Postsubmit
+import android.system.helpers.CommandsHelper
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test rotating an immersive app in fullscreen.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest`
+ *
+ * Actions:
+ * ```
+ * Rotate the device by 90 degrees to trigger a rotation through sensors
+ * Verify that the button exists
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions that are inherited from
+ * the `BaseTest` are ignored due to the nature of the immersive apps.
+ *
+ * This test only works with Cuttlefish devices.
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RotateImmersiveAppInFullscreenTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+ private val immersiveApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.PortraitImmersiveActivity.LABEL,
+ component = ActivityOptions.PortraitImmersiveActivity.COMPONENT.toFlickerComponent()
+ )
+
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+ private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
+
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+
+ private val isCuttlefishDevice: Boolean = Build.MODEL.contains("Cuttlefish")
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ immersiveApp.launchViaIntent(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Display not found")
+ }
+ transitions {
+ if (isCuttlefishDevice) {
+ // Simulates a device rotation through sensors because the rotation button
+ // only appears in a rotation event through sensors
+ execAdb("/vendor/bin/cuttlefish_sensor_injection rotate 0")
+ // verify rotation button existence
+ val rotationButtonSelector = By.res(LAUNCHER_PACKAGE, "rotate_suggestion")
+ uiDevice.wait(Until.hasObject(rotationButtonSelector), FIND_TIMEOUT)
+ uiDevice.findObject(rotationButtonSelector)
+ ?: error("rotation button not found")
+ }
+ }
+ teardown { immersiveApp.exit(wmHelper) }
+ }
+
+ @Before
+ fun setUpForImmersiveAppTests() {
+ Assume.assumeTrue(isCuttlefishDevice)
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarWindowIsAlwaysVisible() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarWindowIsAlwaysVisible() {}
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarWindowIsAlwaysVisible() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerPositionAtStartAndEnd() {}
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {}
+
+ /** Test that app is fullscreen by checking status bar and task bar visibility. */
+ @Postsubmit
+ @Test
+ fun appWindowFullScreen() {
+ flicker.assertWmEnd {
+ this.isAppWindowInvisible(ComponentNameMatcher.STATUS_BAR)
+ .isAppWindowInvisible(ComponentNameMatcher.TASK_BAR)
+ .visibleRegion(immersiveApp)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Test that app is in the original rotation we have set up. */
+ @Postsubmit
+ @Test
+ fun appInOriginalRotation() {
+ flicker.assertWmEnd { this.hasRotation(Rotation.ROTATION_90) }
+ }
+
+ companion object {
+ private var startDisplayBounds = Rect.EMPTY
+ const val LAUNCHER_PACKAGE = "com.google.android.apps.nexuslauncher"
+
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90),
+ // TODO(b/292403378): 3 button mode not added as rotation button is hidden in
+ // taskbar
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
index ea0392cee95a..9792c859cced 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -33,20 +33,20 @@ import org.junit.Rule
abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
- protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
- instrumentation,
- launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
- component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
- )
- protected val letterboxTranslucentApp = LetterboxAppHelper(
- instrumentation,
- launcherName = ActivityOptions.TransparentActivity.LABEL,
- component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
- )
+ protected val letterboxTranslucentLauncherApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp =
+ LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
- @JvmField
- @Rule
- val letterboxRule: LetterboxRule = LetterboxRule()
+ @JvmField @Rule val letterboxRule: LetterboxRule = LetterboxRule()
@Before
fun before() {
@@ -54,10 +54,7 @@ abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(f
}
protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
- device.wait(
- Until.findObject(By.text("Launch Transparent")),
- FIND_TIMEOUT
- )
+ device.wait(Until.findObject(By.text("Launch Transparent")), FIND_TIMEOUT)
protected fun FlickerTestData.goBack() = device.pressBack()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
new file mode 100644
index 000000000000..c4e9a8479563
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsBubbles",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.bubbles",
+ instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml
new file mode 100644
index 000000000000..e6e6f1b21bc3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker.bubble">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.bubble"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..1df11369a049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS b/libs/WindowManager/Shell/tests/flicker/bubble/OWNERS
index 566acc87e42d..566acc87e42d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml
index 5a8155a66d30..4bd9ca049f55 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/res/xml/network_security_config.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2023 The Android Open Source Project
~
@@ -14,11 +15,8 @@
~ limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.pip">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.pip"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 5c7d1d8df2e8..0c36e29a8315 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -31,6 +31,7 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
+import com.android.server.wm.flicker.helpers.MultiWindowUtils
import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
@@ -47,7 +48,7 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker)
private val uid =
context.packageManager
- .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0))
+ .getApplicationInfo(testApp.packageName, PackageManager.ApplicationInfoFlags.of(0))
.uid
@JvmOverloads
@@ -56,8 +57,12 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker)
): FlickerBuilder.() -> Unit {
return {
setup {
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings put secure force_hide_bubbles_user_education 1"
+ )
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_ALL
)
@@ -67,8 +72,12 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker)
}
teardown {
+ MultiWindowUtils.executeShellCommand(
+ instrumentation,
+ "settings put secure force_hide_bubbles_user_education 0"
+ )
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_NONE
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index bc565bc5fd42..55039f59190b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,12 +17,11 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import androidx.test.filters.FlakyTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
@@ -40,12 +39,10 @@ import org.junit.runners.Parameterized
* Switch in different bubble notifications
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-open class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
- BaseBubbleScreen(flicker) {
+class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 3f28ae848d1f..9ca7bf113589 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -20,12 +20,12 @@ import android.content.Context
import android.graphics.Point
import android.platform.test.annotations.Presubmit
import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -42,10 +42,9 @@ import org.junit.runners.Parameterized
* Dismiss a bubble notification
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
@@ -80,7 +79,8 @@ open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbl
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp)
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(testApp, ComponentNameMatcher(className = "Bubbles!#"))
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 26aca1830889..b007e6b3535c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.bubble
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -24,6 +23,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.view.WindowInsets
import android.view.WindowManager
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 508539411aa0..4959672d865b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -20,7 +20,6 @@ import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -39,10 +38,9 @@ import org.junit.runners.Parameterized
* The activity for the bubble is launched
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index a926bb7d85c3..0d95574aca06 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -20,7 +20,6 @@ import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.junit.Test
@@ -38,10 +37,9 @@ import org.junit.runners.Parameterized
* Send a bubble notification
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
+class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
deleted file mode 100644
index 887d8db3042f..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.splitscreen">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.splitscreen"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
new file mode 100644
index 000000000000..386983ce6aae
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -0,0 +1,136 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip1-src",
+ srcs: [
+ "src/**/A*.kt",
+ "src/**/B*.kt",
+ "src/**/C*.kt",
+ "src/**/D*.kt",
+ "src/**/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip2-src",
+ srcs: [
+ "src/**/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip3-src",
+ srcs: ["src/**/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPipCommon-src",
+ srcs: ["src/**/common/*.kt"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPipApps-src",
+ srcs: ["src/**/apps/*.kt"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip2",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsPip3-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipApps-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPipApps",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.pip.apps",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ srcs: [
+ ":WMShellFlickerTestsPipApps-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPipAppsCSuite",
+ defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
+ additional_manifests: ["AndroidManifest.xml"],
+ package_name: "com.android.wm.shell.flicker.pip.apps",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ srcs: [
+ ":WMShellFlickerTestsPipApps-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+ test_suites: [
+ "device-tests",
+ "csuite",
+ ],
+}
+
+csuite_test {
+ name: "csuite-1p3p-pip-flickers",
+ test_config_template: "csuiteDefaultTemplate.xml",
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml
new file mode 100644
index 000000000000..6d5423b28f39
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidManifest.xml
@@ -0,0 +1,80 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker.pip">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <!-- Enable mocking GPS location -->
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.pip"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..1df11369a049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS b/libs/WindowManager/Shell/tests/flicker/pip/OWNERS
index 172e24bf4574..172e24bf4574 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS
+++ b/libs/WindowManager/Shell/tests/flicker/pip/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
new file mode 100644
index 000000000000..ca182fa5a266
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests WMShellFlickerTestsPipAppsCSuite">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="WMShellFlickerTestsPipAppsCSuite.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.wm.shell.flicker.pip.apps"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip.apps/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+
+ <!-- Needed for installing apk's from Play Store -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="aapt-version" value="AAPT2"/>
+ <option name="throw-if-not-found" value="false"/>
+ <option name="install-arg" value="-d"/>
+ <option name="install-arg" value="-g"/>
+ <option name="install-arg" value="-r"/>
+ <option name="test-file-name" value="pstash://com.netflix.mediaclient"/>
+ </target_preparer>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml
index 437871f1bb8f..4bd9ca049f55 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/res/xml/network_security_config.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2023 The Android Open Source Project
~
@@ -14,11 +15,8 @@
~ limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.wm.shell.flicker.bubble">
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker.bubble"
- android:label="WindowManager Flicker Tests">
- </instrumentation>
-</manifest>
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index c335d3dc7f4b..a5c2c8988e70 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -80,12 +80,17 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
secondAppForSplitScreen.launchViaIntent(wmHelper)
pipApp.launchViaIntent(wmHelper)
tapl.goHome()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ pipApp,
+ secondAppForSplitScreen,
+ flicker.scenario.startRotation
+ )
pipApp.enableAutoEnterForPipActivity()
}
teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
pipApp.exit(wmHelper)
secondAppForSplitScreen.exit(wmHelper)
}
@@ -126,9 +131,18 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
if (tapl.isTablet) {
flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
} else {
- // on phones home does not rotate in landscape, PiP enters back to portrait
- // orientation so use display bounds from that orientation for assertion
- flicker.assertWmVisibleRegion(pipApp) { coversAtMost(portraitDisplayBounds) }
+ // on phones home screen does not rotate in landscape, PiP enters back to portrait
+ // orientation - if we go from landscape to portrait it should switch between the bounds
+ // otherwise it should be the same as tablet (i.e. portrait to portrait)
+ if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
+ flicker.assertWmVisibleRegion(pipApp) {
+ // first check against landscape bounds then against portrait bounds
+ coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
+ }
+ } else {
+ // always check against the display bounds which do not change during transition
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 2f7a25ea586d..af2db12faf3a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -20,7 +20,9 @@ import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -53,8 +55,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
+open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -64,15 +65,9 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
}
}
- override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- }
+ override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
- @Presubmit
+ @FlakyTest(bugId = 293133362)
@Test
override fun pipLayerReduces() {
flicker.assertLayers {
@@ -84,7 +79,7 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
}
/** Checks that [pipApp] window is animated towards default position in right bottom corner */
- @Presubmit
+ @FlakyTest(bugId = 255578530)
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
@@ -107,4 +102,10 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
+
+ @FlakyTest(bugId = 289943985)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 68bc9a28967e..9256725c6180 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -21,7 +21,7 @@ import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,11 +49,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions {
val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index dc48696f3197..002c019eff93 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,11 +49,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.closePipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 5e392628aa6a..4cc9547ba2e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -40,11 +40,10 @@ import org.junit.runners.Parameterized
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -54,13 +53,7 @@ open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTra
}
}
- override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- }
+ override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ec35837bc8dd..8207b85c3e0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -28,13 +27,14 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -65,11 +65,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = FixedOrientationAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 76c811cbbeea..cc943678d492 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -46,7 +46,6 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index b80b7483ba4d..7da442901e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -48,11 +48,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index f003ed8a77e0..0ad9e4c61d83 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -47,11 +47,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index a1d3a117482e..89a6c93e478c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -23,7 +23,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,11 +51,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.doubleClickPipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 8c8d280aea9a..8978af0088b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -22,7 +22,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,11 +30,10 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/** Test expanding a pip window via pinch out gesture. */
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 421ad757f76a..4776206724cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -21,6 +21,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index dffc822e7aec..425cbfaffedd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -25,9 +25,9 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,11 +35,10 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
override val thisTransition: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 992f1bc4ace3..18f30d96938b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -21,6 +21,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 3095cac94598..36047cca55ea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -16,20 +16,35 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+/** Test changing aspect ratio of pip. */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) :
- ExpandPipOnDoubleClickTest(flicker) {
+class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.changeAspectRatio() }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAspectRatioChangesProperly() {
+ flicker.assertLayersStart { this.visibleRegion(pipApp).isSameAspectRatio(16, 9) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).isSameAspectRatio(1, 2) }
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 0c6fc5636a5b..c7f2786debd0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -23,6 +23,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index de64f78a31eb..cabc1cc0b023 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,16 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.graphics.Rect
-import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,6 +35,7 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
+@FlakyTest(bugId = 294993100)
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -80,8 +82,8 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
/**
* Checks that the visible region area of [pipApp] moves to closest edge during the animation.
*/
- @Presubmit
@Test
+ @FlakyTest(bugId = 294993100)
fun pipLayerMovesToClosestEdge() {
flicker.assertLayers {
val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
@@ -95,6 +97,90 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
}
}
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun hasAtMostOnePipDismissOverlayWindow() {
+ super.hasAtMostOnePipDismissOverlayWindow()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ // Overridden to remove @Presubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 0295741bc441..381f947cbc84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -18,11 +18,13 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.subject.exceptions.IncorrectRegionException
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,14 +41,26 @@ class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
}
- /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+ /**
+ * Checks that the visible region area of [pipApp] decreases and then increases during the
+ * animation.
+ */
@Presubmit
@Test
- fun pipLayerAreaDecreases() {
+ fun pipLayerAreaDecreasesThenIncreases() {
+ val isAreaDecreasing = arrayOf(true)
flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
- current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ if (isAreaDecreasing[0]) {
+ try {
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ } catch (e: IncorrectRegionException) {
+ isAreaDecreasing[0] = false
+ }
+ } else {
+ previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c315e744bd55..1f69847e5481 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -27,10 +26,12 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 0ff9cfff873e..308ece40402f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -23,9 +23,9 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,11 +55,10 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
-@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
new file mode 100644
index 000000000000..be5a27ac7dcc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ protected abstract val standardAppHelper: StandardAppHelper
+
+ /** Checks [standardAppHelper] window remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppWindowAlwaysVisible() {
+ flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+ }
+
+ /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+ }
+
+ /** Checks the content overlay appears then disappears during the animation */
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ super.pipOverlayLayerAppearThenDisappear()
+ }
+
+ /**
+ * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Postsubmit
+ @Test
+ override fun pipWindowRemainInsideVisibleBounds() {
+ flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ coversAtMost(displayBounds)
+ }
+ }
+
+ /**
+ * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+ * whole animation
+ */
+ @Postsubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ flicker.assertLayersVisibleRegion(
+ standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ ) {
+ coversAtMost(displayBounds)
+ }
+ }
+
+ /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+ @Postsubmit
+ @Test
+ override fun pipLayerReduces() {
+ flicker.assertLayers {
+ val pipLayerList =
+ this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ /** Checks that [standardAppHelper] window becomes pinned */
+ @Postsubmit
+ @Test
+ override fun pipWindowBecomesPinned() {
+ flicker.assertWm {
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+ .then()
+ .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+ }
+ }
+
+ /** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
+ @Postsubmit
+ @Test
+ override fun launcherLayerBecomesVisible() {
+ super.launcherLayerBecomesVisible()
+ }
+
+ /**
+ * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+ * closing the pip window
+ */
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ flicker.assertEventLog {
+ this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+ }
+ }
+
+ @Postsubmit
+ @Test
+ override fun hasAtMostOnePipDismissOverlayWindow() = super.hasAtMostOnePipDismissOverlayWindow()
+
+ // ICommonAssertions.kt overrides due to Morris overlay
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+ *
+ * Note: Phones only
+ */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ // this fails due to Morris overlay
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+ */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+ *
+ * Note: Large screen only
+ */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+ * transition
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** Checks that all parts of the screen are covered during the transition */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
new file mode 100644
index 000000000000..4da52ef1272c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.apps
+
+import android.content.Context
+import android.location.Criteria
+import android.location.Location
+import android.location.LocationManager
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.apphelpers.MapsAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Maps app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch Maps and start navigation mode
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+
+ val locationManager: LocationManager =
+ instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ val mainHandler = Handler(Looper.getMainLooper())
+ var mockLocationEnabled = false
+
+ val updateLocation =
+ object : Runnable {
+ override fun run() {
+ // early bail out if mocking location is not enabled
+ if (!mockLocationEnabled) return
+ val location = Location("Googleplex")
+ location.latitude = 37.42243438411294
+ location.longitude = -122.08426281892311
+ location.time = System.currentTimeMillis()
+ location.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
+ location.accuracy = 100f
+ locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location)
+ mainHandler.postDelayed(this, 5)
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ locationManager.addTestProvider(
+ LocationManager.GPS_PROVIDER,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_FINE
+ )
+ locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true)
+ mockLocationEnabled = true
+ mainHandler.post(updateLocation)
+
+ // normal app open through the Launcher All Apps
+ // var mapsAddressOption = "Golden Gate Bridge"
+ // standardAppHelper.open()
+ // standardAppHelper.doSearch(mapsAddressOption)
+ // standardAppHelper.getDirections()
+ // standardAppHelper.startNavigation();
+
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
+ )
+
+ standardAppHelper.waitForNavigationToStart()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ standardAppHelper.exit(wmHelper)
+ mainHandler.removeCallbacks(updateLocation)
+ // the main looper callback might have tried to provide a new location after the
+ // provider is no longer in test mode, causing a crash, this prevents it from happening
+ mockLocationEnabled = false
+ locationManager.removeTestProvider(LocationManager.GPS_PROVIDER)
+ }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
new file mode 100644
index 000000000000..5498e8c4f970
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.NetflixAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from Netflix app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch Netflix and start playing a video
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+ ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
+ )
+ standardAppHelper.waitForVideoPlaying()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown { standardAppHelper.exit(wmHelper) }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHomeFromImmersiveFullscreenApp() }
+ }
+
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // Netflix uses source rect hint, so PiP overlay is never present
+ }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.statusBarLayerPositionAtEnd()
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+ * orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0),
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
new file mode 100644
index 000000000000..d8afc25caf71
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.apps
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.YouTubeAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.RequiresDevice
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from YouTube app by interacting with the app UI
+ *
+ * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ *
+ * Actions:
+ * ```
+ * Launch YouTube and start playing a video
+ * Go home to enter PiP
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
+ override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ standardAppHelper.launchViaIntent(
+ wmHelper,
+ YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
+ )
+ standardAppHelper.waitForVideoPlaying()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown { standardAppHelper.exit(wmHelper) }
+ }
+
+ override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
+
+ @Postsubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // YouTube uses source rect hint, so PiP overlay is never present
+ }
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.focusChanges()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index a17144b7cef3..751f2bc0807f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6d20740e239c..9c129e47efba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
@@ -91,7 +91,7 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
/** Checks that [pipApp] window becomes pinned */
@Presubmit
@Test
- fun pipWindowBecomesPinned() {
+ open fun pipWindowBecomesPinned() {
flicker.assertWm {
invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
.then()
@@ -102,7 +102,7 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
/** Checks [ComponentNameMatcher.LAUNCHER] layer remains visible throughout the animation */
@Presubmit
@Test
- fun launcherLayerBecomesVisible() {
+ open fun launcherLayerBecomesVisible() {
flicker.assertLayers {
isInvisible(ComponentNameMatcher.LAUNCHER)
.then()
@@ -117,7 +117,7 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index dfffba831dc3..9450bdd2d894 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
index a8fb63de244b..7e42bc11a9c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 096af39488e9..7b7f1d7b5a82 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
import android.app.Instrumentation
import android.content.Intent
@@ -92,7 +92,7 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
@Presubmit
@Test
- fun hasAtMostOnePipDismissOverlayWindow() {
+ open fun hasAtMostOnePipDismissOverlayWindow() {
val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
flicker.assertWm {
val overlaysPerState =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
index 000ae8f9458e..c6cbcd052fe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -26,7 +26,7 @@ import com.android.server.wm.flicker.helpers.PipAppHelper
/** Helper class for PIP app on AndroidTV */
open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
- private val appSelector = By.pkg(`package`).depth(0)
+ private val appSelector = By.pkg(packageName).depth(0)
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
@@ -46,7 +46,7 @@ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instr
}
override fun clickObject(resId: String) {
- val selector = By.res(`package`, resId)
+ val selector = By.res(packageName, resId)
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
@@ -68,7 +68,7 @@ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instr
}
fun waitUntilClosed(): Boolean {
- val appSelector = By.pkg(`package`).depth(0)
+ val appSelector = By.pkg(packageName).depth(0)
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 2cb18f948f0e..2cb18f948f0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 8a073abf032c..8a073abf032c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index d4cd6da4acb1..d4cd6da4acb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 4402e2153e9b..4402e2153e9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8de377e..47bff8de377e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index 4aee61ade10e..4aee61ade10e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
new file mode 100644
index 000000000000..9b8cd94d56b2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerServicePlatinumTests-src",
+ srcs: [
+ "src/**/platinum/*.kt",
+ "src/**/scenarios/*.kt",
+ "src/**/common/*.kt",
+ ],
+}
+
+java_library {
+ name: "wm-shell-flicker-platinum-tests",
+ platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
+ srcs: [
+ ":WMShellFlickerServicePlatinumTests-src",
+ ],
+ static_libs: [
+ "wm-shell-flicker-utils",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerServiceTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: ["src/**/*.kt"],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerServicePlatinumTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: [":WMShellFlickerServicePlatinumTests-src"],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
new file mode 100644
index 000000000000..d54b6941d975
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker.service">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.service"
+ android:label="WindowManager Flicker Service Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..1df11369a049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
new file mode 100644
index 000000000000..4bd9ca049f55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
index 610cedefe594..4bd79546b96d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service
+package com.android.wm.shell.flicker.service.common
import android.app.Instrumentation
import android.platform.test.rule.NavigationModeRule
@@ -23,6 +23,7 @@ import android.platform.test.rule.UnlockScreenRule
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ArtifactSaverRule
import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.flicker.rules.LaunchAppRule
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
@@ -33,10 +34,9 @@ object Utils {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
- return RuleChain.outerRule(UnlockScreenRule())
- .around(
- NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
- )
+ return RuleChain.outerRule(ArtifactSaverRule())
+ .around(UnlockScreenRule())
+ .around(NavigationModeRule(navigationMode.value, false))
.around(
LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
)
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
new file mode 100644
index 000000000000..3ab6a1ee061d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
index a5c512267508..a5c512267508 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
index 092fb6720e57..092fb6720e57 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 8cb25fe531b8..69499b9b488b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -31,7 +31,8 @@ import org.junit.runner.RunWith
class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
index fa1be63296e0..bd627f4babaa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -31,7 +31,8 @@ import org.junit.runner.RunWith
class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ // TODO(b/300260196): Not detecting SPLIT_SCREEN_EXIT right now
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index aa35237b615f..a8f4d0a24c7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) // SPLIT_SCREEN_EXIT not yet tagged here (b/301222449)
@Test
override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index e195360cdc36..cee9bbfb4aa4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) // SPLIT_SCREEN_EXIT not yet tagged here (b/301222449)
@Test
override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
index c1b3aade11cd..c1b3aade11cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
index c6e2e854ab89..c6e2e854ab89 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index 5f771c7545c7..169b5cfa3462 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @ExpectedScenarios(["ENTIRE_TRACE"]) // missing SPLIT_SCREEN_ENTER tag (b/301093332)
@Test
override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 729a401fe34c..412c011a3f17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @ExpectedScenarios(["ENTIRE_TRACE"]) // missing SPLIT_SCREEN_ENTER tag (b/301093332)
@Test
override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 6e4cf9f55cfd..6e4cf9f55cfd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index cc2870213e8d..cc2870213e8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 736604f02377..736604f02377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 8df8dfaab071..8df8dfaab071 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 378f055ef830..378f055ef830 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b33d26288d33..b33d26288d33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index b1d3858b9dbb..b1d3858b9dbb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 6d824c74c1fe..6d824c74c1fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index f1d3d0cf2716..f1d3d0cf2716 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index a867bac8c0eb..a867bac8c0eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 76247ba10037..76247ba10037 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index e179da81e4db..e179da81e4db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 20f554f7d154..20f554f7d154 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index f7776ee3244a..f7776ee3244a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 00f607343b3b..4ff0b4362e60 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
- @ExpectedScenarios(["QUICKSWITCH"])
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
@Test
override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index b3340e77dbfa..930f31d1f348 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
- @ExpectedScenarios(["QUICKSWITCH"])
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
@Test
override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index 3da61e5e310c..3da61e5e310c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 627ae1843314..627ae1843314 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 000000000000..c744103d49ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 000000000000..11a4e02c5e37
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index 92b62273d8cb..e37d806c7a14 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 566adec75615..2a50912e0a5c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index e6d56b5c94d3..d5da1a8b558c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+open class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 6752c58bd568..7fdcb9be62ee 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+open class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 7c9ab9939dd0..308e954b86c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+open class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 4b795713cb23..39e75bd25a71 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+open class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index 71ef48bea686..e18da17175c0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 04950799732e..00d60e756ffa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index c78729c6dc92..d7efbc8c0fd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 30bce2f657b1..4eece3f62d10 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index b33ea7c89158..d96b056d8753 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 07a86a57117b..809b690e0861 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 9a1d12787b9d..bbdf2d728494 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 266e268a3537..5c29fd8fe57e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 83fc30bceb7b..a7398ebf56e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b2f19299c7f0..eae88ad4ad09 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index dae92dddbfec..7e8ee04a28fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+open class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 732047ba38ad..9295c330b879 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+open class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 1de7efd7970a..4b59e9fbd866 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 1a046aa5b09e..5ff36d4aabbb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 6e88f0eddee8..c0cb7219437b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index d26a29c80583..8c140884aa50 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 4a552b0aed6a..7b6614b81c11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index b7376eaea66d..5df5be9daa8b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index b2d05e4a2632..9d63003bf2a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+open class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 6de31b1315e4..9fa04b208ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+open class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index aab18a6d27b9..9386aa2b2cf0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+open class SwitchBetweenSplitPairsGesturalNavLandscape :
SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index b074f2c161c9..5ef21672bfe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+open class SwitchBetweenSplitPairsGesturalNavPortrait :
SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index 840401c23a91..9caab9b5182a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index c402aa4444d8..bf484e5cef98 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index e530f6369609..80ab24ddf9ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -51,7 +51,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index e9fc43746d27..4c391047e853 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index 416692c37b34..f6d1afc05a5a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 494a246d2f50..db5a32a382fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 369bdfc1103d..d7b306c3be23 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -19,13 +19,15 @@ package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -46,11 +48,15 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
+ Assume.assumeTrue(tapl.isTablet)
tapl.goHome()
+
primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
}
@Test
@@ -58,7 +64,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 776c397cc354..cc982d1ba860 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,13 +19,15 @@ package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -47,14 +49,18 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
+ Assume.assumeTrue(tapl.isTablet)
// Send a notification
sendNotificationApp.launchViaIntent(wmHelper)
sendNotificationApp.postNotification(wmHelper)
tapl.goHome()
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 5d67dc7e231b..fa12bb869467 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,11 +19,12 @@ package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
@@ -49,12 +50,13 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
Assume.assumeTrue(tapl.isTablet)
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
-
tapl.goHome()
SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
}
@Test
@@ -63,7 +65,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
// TODO: Do we want this check in here? Add to the other tests?
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 5bbb42fd1864..2592fd40d902 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -23,9 +23,10 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -46,6 +47,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
@@ -58,7 +61,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
open fun enterSplitScreenByDragFromTaskbar() {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index c2100f641a55..983653b9b5ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -46,9 +46,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
- tapl.setEnableRotation(true)
- tapl.setExpectedRotation(rotation.value)
-
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
@@ -57,11 +54,14 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
.withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
}
@Test
open fun enterSplitScreenFromOverview() {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 70f3bed9afdc..068171d2e129 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -25,7 +25,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -48,11 +48,12 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- tapl.workspace.switchToOverview().dismissAllTasks()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 86f394da0231..64b75c5fd967 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -50,7 +50,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index d7b611e04d9d..179501089168 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index bf4c381b1c3a..7065846dc653 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -46,10 +46,12 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Before
fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 4a9c32f10415..251cb50de017 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -51,8 +51,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 383a6b39a2b6..a9933bbe09fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -23,7 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
new file mode 100644
index 000000000000..4629c5318366
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenBase-src",
+ srcs: [
+ "src/**/benchmark/*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroup1-src",
+ srcs: [
+ "src/**/A*.kt",
+ "src/**/B*.kt",
+ "src/**/C*.kt",
+ "src/**/D*.kt",
+ "src/**/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsSplitScreenGroup2-src",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroup1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
+
+android_test {
+ name: "WMShellFlickerTestsSplitScreenGroup2",
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.wm.shell.flicker.splitscreen",
+ instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ srcs: [
+ ":WMShellFlickerTestsSplitScreenBase-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ],
+ static_libs: ["WMShellFlickerTestsBase"],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
new file mode 100644
index 000000000000..9ff2161daa51
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.flicker.splitscreen">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <!-- Read and write traces from external storage -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Capture screen contents -->
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Run layers trace -->
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+ <!-- Capture screen recording -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+ <!-- Workaround grant runtime permission exception from b/152733071 -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <!-- Force-stop test apps -->
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+ <!-- Control test app's media session -->
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+ <application android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config"
+ android:largeHeap="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <service android:name=".NotificationListener"
+ android:exported="true"
+ android:label="WMShellTestsNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+
+ <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ tools:node="remove" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+ android:label="WindowManager Flicker Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
new file mode 100644
index 000000000000..1df11369a049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+ <option name="test-tag" value="FlickerTests"/>
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on"/>
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true"/>
+ <!-- set WM tracing verbose level to all -->
+ <option name="run-command" value="cmd window tracing level all"/>
+ <!-- set WM tracing to frame (avoid incomplete states) -->
+ <option name="run-command" value="cmd window tracing frame"/>
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+ <!-- ensure lock screen mode is swipe -->
+ <option name="run-command" value="locksettings set-disabled false"/>
+ <!-- restart launcher to activate TAPL -->
+ <option name="run-command"
+ value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480"/>
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="test-user-token" value="%TEST_USER%"/>
+ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+ <option name="run-command" value="settings put system show_touches 1"/>
+ <option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard"/>
+ <option name="teardown-command" value="settings delete system show_touches"/>
+ <option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="{MODULE}.apk"/>
+ <option name="test-file-name" value="FlickerTestApp.apk"/>
+ </target_preparer>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="{PACKAGE}"/>
+ <option name="shell-timeout" value="6600s"/>
+ <option name="test-timeout" value="6000s"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true"/>
+ </test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
new file mode 100644
index 000000000000..3ab6a1ee061d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
new file mode 100644
index 000000000000..4bd9ca049f55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 3702be9541a3..6b971699d212 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -24,6 +23,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 8b906305506f..51588569a8aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 50f6a382a702..fc6c2b3d7ce7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index ca9c13009848..8b1689a9d816 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
@@ -57,7 +57,7 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) :
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun primaryAppLayerVisibilityChanges() {
flicker.assertLayers {
@@ -69,7 +69,7 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) :
}
}
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun secondaryAppLayerVisibilityChanges() {
flicker.assertLayers {
@@ -87,7 +87,7 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) :
@Test
fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
- @FlakyTest(bugId = 245472831)
+ @FlakyTest(bugId = 291678271)
@Test
fun primaryAppBoundsChanges() {
flicker.splitAppLayerBoundsChanges(
@@ -97,7 +97,7 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) :
)
}
- @Presubmit
+ @FlakyTest(bugId = 291678271)
@Test
fun secondaryAppBoundsChanges() =
flicker.splitAppLayerBoundsChanges(
@@ -106,6 +106,12 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) :
portraitPosTop = true
)
+ @FlakyTest(bugId = 291678271)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index f8d1e1f1f498..99613f39060d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ff5d93550541..756a7fa4ba98 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 7c710777087d..121b46acdb66 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 83717062b05e..99deb9279271 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -24,6 +23,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 0bfdbb4de7c5..212a4e3649dc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index fac97c8cc8c4..fac97c8cc8c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 88bbc0e7880b..284c32ea110d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index e85dc24a7781..9e6448f0bec9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index f7a9ed073002..8e28712cd993 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,13 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 66f9b85ea572..fb0193b1830d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -16,12 +16,12 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
new file mode 100644
index 000000000000..715a533a7bab
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch between two split pairs.
+ *
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairsNoPip`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) :
+ SplitScreenBase(flicker) {
+
+ val thirdApp = SplitScreenUtils.getSendNotification(instrumentation)
+ val pipApp = PipAppHelper(instrumentation)
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ thirdApp,
+ pipApp,
+ flicker.scenario.startRotation
+ )
+ pipApp.enableAutoEnterForPipActivity()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, pipApp)
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ teardown {
+ pipApp.exit(wmHelper)
+ thirdApp.exit(wmHelper)
+ }
+ }
+
+ /** Checks that [pipApp] window won't enter pip */
+ @Presubmit
+ @Test
+ fun notEnterPip() {
+ flicker.assertWm { isNotPinned(pipApp) }
+ }
+
+ /** Checks the [pipApp] task did not reshow during transition. */
+ @Presubmit
+ @Test
+ fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ .then()
+ .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isInvisible(pipApp)
+ .isVisible(secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
+
+ /** Checks the [pipApp] task become invisible after transition finish. */
+ @Presubmit @Test fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp)
+
+ /** Checks the [pipApp] task is in split screen bounds when transition start. */
+ @Presubmit
+ @Test
+ fun pipAppBoundsIsVisibleAtBegin() =
+ flicker.assertLayersStart {
+ this.splitAppLayerBoundsSnapToDivider(
+ pipApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true,
+ flicker.scenario.startRotation
+ )
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 851391d37323..f3145c97a6f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -17,12 +17,16 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
@@ -57,6 +61,22 @@ class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) :
}
@Test
+ @FlakyTest(bugId = 293578017)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ // TODO(b/293578017) remove once that bug is resolve
+ @Test
+ @Presubmit
+ fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() =
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(WALLPAPER_BBQ_WRAPPER)
+ )
+ }
+
+ @Test
fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index e5c1e75a75f4..df1c9a2ec089 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,16 @@ abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTe
protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ textEditApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
SplitScreenUtils.copyContentInSplit(
instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index e4e1af9d24ce..d01eab060263 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -21,7 +21,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -36,7 +35,16 @@ abstract class DismissSplitScreenByDividerBenchmark(override val flicker: Legacy
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
if (tapl.isTablet) {
SplitScreenUtils.dragDividerToDismissSplit(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index b2dd02bf2c41..e36bd33bd1fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -21,7 +21,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -36,7 +35,16 @@ abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyF
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 078859166dbc..050d389e978c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -21,7 +21,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
@@ -38,7 +37,16 @@ abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerT
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 884e4513e893..394864ad9d4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
@@ -48,7 +47,7 @@ abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker:
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index e5c40b69726c..cd3fbab1497b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 04510014c437..3b3be84f9841 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
@@ -54,7 +53,7 @@ abstract class EnterSplitScreenByDragFromShortcutBenchmark(
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index 9e0ca1b20f09..eff355987cc0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
@@ -47,7 +46,7 @@ abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker:
transitions {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 06b4fe7e0eb4..5e5e7d7fc3c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -21,7 +21,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -47,7 +46,7 @@ abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: Legac
.waitForAndVerify()
}
transitions {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, flicker.scenario.startRotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
index 7ce995ac64aa..a0e437c25aa7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SplitScreenBase.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.splitscreen.benchmark
import android.content.Context
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -33,7 +33,10 @@ abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(f
tapl.setEnableRotation(true)
setRotation(flicker.scenario.startRotation)
tapl.setExpectedRotation(flicker.scenario.startRotation.value)
- tapl.workspace.switchToOverview().dismissAllTasks()
+ val overview = tapl.workspace.switchToOverview()
+ if (overview.hasTasks()) {
+ overview.dismissAllTasks()
+ }
}
}
@@ -43,11 +46,4 @@ abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(f
secondaryApp.exit(wmHelper)
}
}
-
- protected open val withoutTracing: FlickerBuilder.() -> Unit = {
- withoutLayerTracing()
- withoutWindowManagerTracing()
- withoutTransitionTracing()
- withoutTransactionsTracing()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 007b7518b16e..e39c3c93d79a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -25,7 +25,6 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,16 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ }
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 10c8eebf7d1d..32284ba41aee 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,7 +39,14 @@ abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Le
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a6e750fed70e..a926ec903f58 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -38,7 +37,14 @@ abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFl
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index 7e8d5fb83157..d2e1d5294aa1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -38,7 +37,14 @@ abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: Legacy
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 56edad1ded19..9d6b25174c13 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -21,7 +21,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -40,8 +39,22 @@ abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlic
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ primaryApp,
+ secondaryApp,
+ flicker.scenario.startRotation
+ )
+ SplitScreenUtils.enterSplit(
+ wmHelper,
+ tapl,
+ device,
+ thirdApp,
+ fourthApp,
+ flicker.scenario.startRotation
+ )
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 065d4d62be42..e71834de7123 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
new file mode 100644
index 000000000000..406ada97a07d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 735fbfb341f5..568650d71872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
deleted file mode 100644
index 7a668897fbbe..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) :
- ClosePipBySwipingDownTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
deleted file mode 100644
index 718b14babc4f..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) :
- ClosePipWithDismissButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
deleted file mode 100644
index 2b3e76a964c4..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test will fail because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) :
- EnterPipOnUserLeaveHintTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
deleted file mode 100644
index 92642197e9be..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test fails because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) :
- EnterPipToOtherOrientation(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
deleted file mode 100644
index 78e80497747c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
deleted file mode 100644
index e25c0d6eddc0..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) :
- ExitPipToAppViaExpandButtonTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
deleted file mode 100644
index be19f3cd1970..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) :
- ExitPipToAppViaIntentTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
deleted file mode 100644
index 1a1ce6823f3b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) :
- ExpandPipOnPinchOpenTest(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() =
- LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
deleted file mode 100644
index 63292a4f2ca3..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) :
- MovePipOnImeVisibilityChangeTest(flicker) {
- companion object {
- private const val TAG_IME_VISIBLE = "imeIsVisible"
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_0)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
deleted file mode 100644
index 25164711b2e5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.flicker.assertions.FlickerTest
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) {
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
- * orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.rotationTests()
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
deleted file mode 100644
index 7cbc1c3c272c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
deleted file mode 100644
index 2eb81e0d0492..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index e5c124cbe775..f1cb37ee1293 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -265,6 +265,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
val dividerRegion =
layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+ visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
if (displayBounds.width > displayBounds.height) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 3f8a1ae6bd79..c31b9e2c22c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -19,10 +19,12 @@ package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
+import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.IComponentMatcher
import android.tools.common.traces.component.IComponentNameMatcher
import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import android.tools.device.traces.parsers.toFlickerComponent
import android.view.InputDevice
@@ -101,13 +103,15 @@ object SplitScreenUtils {
tapl: LauncherInstrumentation,
device: UiDevice,
primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ secondaryApp: StandardAppHelper,
+ rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- splitFromOverview(tapl, device)
+ splitFromOverview(tapl, device, rotation)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -121,7 +125,7 @@ object SplitScreenUtils {
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+ fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice, rotation: Rotation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
@@ -129,7 +133,9 @@ object SplitScreenUtils {
// TAPL's currentTask on tablet is sometimes not what we expected if the overview
// contains more than 3 task views. We need to use uiautomator directly to find the
// second task to split.
- tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val home = tapl.workspace.switchToOverview()
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ home.overviewActions.clickSplit()
val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
if (snapshots == null || snapshots.size < 1) {
error("Fail to find a overview snapshot to split.")
@@ -145,13 +151,12 @@ object SplitScreenUtils {
}
snapshots[0].click()
} else {
- tapl.workspace
- .switchToOverview()
- .currentTask
- .tapMenu()
- .tapSplitMenuItem()
- .currentTask
- .open()
+ val rotationCheckEnabled = tapl.getExpectedRotationCheckEnabled()
+ tapl.setExpectedRotationCheckEnabled(false) // disable rotation check to enter overview
+ val home = tapl.workspace.switchToOverview()
+ tapl.setExpectedRotationCheckEnabled(rotationCheckEnabled) // restore rotation checks
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ home.currentTask.tapMenu().tapSplitMenuItem().currentTask.open()
}
SystemClock.sleep(TIMEOUT_MS)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index d09a90cd7dc7..aadadd604d3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -35,6 +35,7 @@ android_test {
static_libs: [
"WindowManager-Shell",
"junit",
+ "flag-junit-base",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
@@ -49,6 +50,7 @@ android_test {
"testables",
"platform-test-annotations",
"servicestests-utils",
+ "com_android_wm_shell_flags_lib",
],
libs: [
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 2d9304705738..02c9d306f4bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -35,7 +35,7 @@ import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
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 270dbc49835f..974d69b2ac5d 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
@@ -39,7 +39,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -187,6 +187,25 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mFinishTransaction);
}
+ @Test
+ public void testShouldAnimate_containsAnimationOptions() {
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
+ .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
+ .build();
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
+ 0 /* backgroundColor */, false /* overrideTaskTransition */));
+ assertTrue(mController.shouldAnimate(info));
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions());
+ assertFalse(mController.shouldAnimate(info));
+
+ info.setAnimationOptions(TransitionInfo.AnimationOptions.makeCrossProfileAnimOptions());
+ assertFalse(mController.shouldAnimate(info));
+ }
+
@UiThreadTest
@Test
public void testMergeAnimation() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
index 17ed396987af..e7274918fa2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
@@ -424,6 +424,7 @@ class PhysicsAnimatorTest : ShellTestCase() {
eq(-5f), anyFloat(), eq(true))
}
+ @Ignore("Started flaking despite no changes, tracking in b/299636216")
@Test
fun testIsPropertyAnimating() {
PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
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 3d8bd3854a45..6cf5450db9ea 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
@@ -37,6 +37,7 @@ import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
@@ -44,9 +45,9 @@ import android.os.RemoteException;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContentResolver;
-import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -58,7 +59,6 @@ import android.window.IOnBackInvokedCallback;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
@@ -67,8 +67,8 @@ 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.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -85,12 +85,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
- private ShellInit mShellInit;
-
- @Rule
- public TestableContext mContext =
- new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+ private ShellInit mShellInit;
@Mock
private IActivityTaskManager mActivityTaskManager;
@@ -112,13 +108,19 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private BackAnimationBackground mAnimationBackground;
+ @Mock
+ private InputManager mInputManager;
+
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
+ private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(InputManager.class, mInputManager);
mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
mContentResolver = new TestableContentResolver(mContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
@@ -126,11 +128,23 @@ public class BackAnimationControllerTest extends ShellTestCase {
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
- mController.setEnableUAnimation(true);
+ mShellBackAnimationRegistry =
+ new ShellBackAnimationRegistry(
+ new CrossActivityAnimation(mContext, mAnimationBackground),
+ new CrossTaskBackAnimation(mContext, mAnimationBackground),
+ new CustomizeActivityAnimation(mContext, mAnimationBackground),
+ null);
+ mController =
+ new BackAnimationController(
+ mShellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -138,12 +152,13 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void createNavigationInfo(int backType,
boolean enableAnimation,
boolean isAnimationCallback) {
- BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
- .setType(backType)
- .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(mAppCallback)
- .setPrepareRemoteAnimation(enableAnimation)
- .setAnimationCallback(isAnimationCallback);
+ BackNavigationInfo.Builder builder =
+ new BackNavigationInfo.Builder()
+ .setType(backType)
+ .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation)
+ .setAnimationCallback(isAnimationCallback);
createNavigationInfo(builder);
}
@@ -188,25 +203,28 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void verifyNavigationFinishes() throws RemoteException {
- final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE,
- BackNavigationInfo.TYPE_CALLBACK };
-
- for (int type: testTypes) {
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK
+ };
+
+ for (int type : testTypes) {
registerAnimation(type);
}
- for (int type: testTypes) {
- final ResultListener result = new ResultListener();
+ for (int type : testTypes) {
+ final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
.setOnBackInvokedCallback(mAppCallback)
.setPrepareRemoteAnimation(true)
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
simulateRemoteAnimationFinished();
@@ -230,7 +248,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);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -256,7 +274,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -275,10 +293,17 @@ 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, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
+ mController =
+ new BackAnimationController(
+ shellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -302,14 +327,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
- public void ignoresGesture_transitionInProgress() throws RemoteException {
+ public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
/* enableAnimation = */ true,
/* isAnimationCallback = */ false);
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
releaseBackGesture();
// Check that back invocation is dispatched.
@@ -319,24 +344,64 @@ public class BackAnimationControllerTest extends ShellTestCase {
reset(mAnimatorCallback);
reset(mBackAnimationRunner);
- // Verify that we prevent animation from restarting if another gestures happens before
- // the previous transition is finished.
- doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+ // Verify that we prevent any interaction with the animator callback in case a new gesture
+ // starts while the current back animation has not ended, instead the gesture is queued
+ triggerBackGesture();
verifyNoMoreInteractions(mAnimatorCallback);
- // Finish back navigation.
+ // Finish previous back navigation.
simulateRemoteAnimationFinished();
- // Verify that more events from a rejected swipe cannot start animation.
+ // Verify that releasing the gesture causes back key to be injected
+ releaseBackGesture();
+ verify(mInputManager, times(2))
+ .injectInputEvent(any(KeyEvent.class), any(Integer.class));
+
+ // Verify that we start accepting gestures again once transition finishes.
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- doMotionEvent(MotionEvent.ACTION_UP, 0);
+
+ simulateRemoteAnimationStart();
+ verify(mAnimatorCallback).onBackStarted(any());
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ }
+
+ @Test
+ public void queuedFinishedGesture_RunsAfterPreviousTransitionEnded() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ /* enableAnimation = */ true,
+ /* isAnimationCallback = */ false);
+
+ triggerBackGesture();
+ simulateRemoteAnimationStart();
+ releaseBackGesture();
+
+ // Check that back invocation is dispatched.
+ verify(mAnimatorCallback).onBackInvoked();
+ verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
+
+ reset(mAnimatorCallback);
+ reset(mBackAnimationRunner);
+
+ // Verify that we prevent any interaction with the animator callback in case a new gesture
+ // starts while the current back animation has not ended, instead the gesture is queued
+ triggerBackGesture();
+ releaseBackGesture();
verifyNoMoreInteractions(mAnimatorCallback);
+ // Finish previous back navigation.
+ simulateRemoteAnimationFinished();
+
+ // Verify that back key press is injected after previous back navigation has ended
+ verify(mInputManager, times(2))
+ .injectInputEvent(any(KeyEvent.class), any(Integer.class));
+
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@@ -352,7 +417,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
@@ -363,7 +428,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
}
@@ -379,7 +444,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);
+ simulateRemoteAnimationStart();
verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -398,17 +463,19 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void animationNotDefined() throws RemoteException {
- final int[] testTypes = new int[] {
- BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE};
-
- for (int type: testTypes) {
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE
+ };
+
+ for (int type : testTypes) {
unregisterAnimation(type);
}
- for (int type: testTypes) {
+ for (int type : testTypes) {
final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
@@ -416,7 +483,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setPrepareRemoteAnimation(true)
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
mShellExecutor.flushAll();
releaseBackGesture();
@@ -468,16 +535,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
public void testBackToActivity() throws RemoteException {
final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
}
@Test
public void testBackToTask() throws RemoteException {
final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
}
private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
@@ -502,7 +567,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
- simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationStart();
verify(callback).onBackStarted(any(BackMotionEvent.class));
verify(animationRunner).startAnimation(any(), any(), any(), any());
@@ -527,7 +592,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
/* swipeEdge */ BackEvent.EDGE_LEFT);
}
- private void simulateRemoteAnimationStart(int type) throws RemoteException {
+ private void simulateRemoteAnimationStart() throws RemoteException {
RemoteAnimationTarget animationTarget = createAnimationTarget();
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
@@ -539,12 +604,16 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void simulateRemoteAnimationFinished() {
mController.onBackAnimationFinished();
- mController.finishBackNavigation();
+ mController.finishBackNavigation(/*triggerBack*/ true);
}
private void registerAnimation(int type) {
- mController.registerAnimation(type,
- new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ mController.registerAnimation(
+ type,
+ new BackAnimationRunner(
+ mAnimatorCallback,
+ mBackAnimationRunner,
+ mContext));
}
private void unregisterAnimation(int type) {
@@ -554,10 +623,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
private static class ResultListener implements RemoteCallback.OnResultListener {
boolean mBackNavigationDone = false;
boolean mTriggerBack = false;
+
@Override
public void onResult(@Nullable Bundle result) {
mBackNavigationDone = true;
mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
}
- };
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index e7d459893ce8..cebbbd890f05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -102,15 +102,17 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
@@ -133,15 +135,17 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
} catch (RemoteException r) {
fail("onBackCancelled throw remote exception");
}
@@ -155,11 +159,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
// start animation without any remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4a55429eacb6..26c73946c1c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -111,6 +111,8 @@ public class BubbleDataTest extends ShellTestCase {
@Mock
private BubbleLogger mBubbleLogger;
@Mock
+ private BubbleEducationController mEducationController;
+ @Mock
private ShellExecutor mMainExecutor;
@Captor
@@ -191,7 +193,7 @@ public class BubbleDataTest extends ShellTestCase {
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
mMainExecutor);
// Used by BubbleData to set lastAccessedTime
@@ -385,6 +387,65 @@ public class BubbleDataTest extends ShellTestCase {
assertOverflowChangedTo(ImmutableList.of());
}
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is not required
+ */
+ @Test
+ public void test_shouldNotShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(false);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
+ /**
+ * Verifies that the update should show the user education, if the education is required
+ */
+ @Test
+ public void test_shouldShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isTrue();
+ }
+
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is required but
+ * the bubble should auto-expand
+ */
+ @Test
+ public void test_shouldShowEducation_shouldAutoExpand() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+ mBubbleA1.setShouldAutoExpand(true);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
// COLLAPSED / ADD
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index 0dc16f44340f..cb29a21e2f5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.bubbles;
import static com.google.common.truth.Truth.assertThat;
@@ -27,10 +27,7 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.bubbles.BubbleOverflow;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.TestableBubblePositioner;
+import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 58d9a6486ff2..287a97c9b5b0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -22,20 +22,24 @@ import static android.view.View.LAYOUT_DIRECTION_LTR;
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import android.util.DisplayMetrics;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -257,6 +261,27 @@ public class BubblePositionerTest extends ShellTestCase {
assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
}
+ @Test
+ public void testExpandedViewHeight_onLargeTablet() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ new WindowManagerConfig()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .setUpConfig();
+ mPositioner.update();
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ int manageButtonHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ float expectedHeight = 1800 - 2 * 20 - manageButtonHeight;
+ assertThat(mPositioner.getExpandedViewHeight(bubble)).isWithin(0.1f).of(expectedHeight);
+ }
+
/**
* Calculates the Y position bubbles should be placed based on the config. Based on
* the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
@@ -323,6 +348,8 @@ public class BubblePositionerTest extends ShellTestCase {
? MIN_WIDTH_FOR_TABLET
: MIN_WIDTH_FOR_TABLET - 1;
mConfiguration.orientation = mOrientation;
+ mConfiguration.screenWidthDp = pxToDp(mScreenBounds.width());
+ mConfiguration.screenHeightDp = pxToDp(mScreenBounds.height());
when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
WindowInsets windowInsets = mock(WindowInsets.class);
@@ -331,5 +358,11 @@ public class BubblePositionerTest extends ShellTestCase {
when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
}
+
+ private int pxToDp(float px) {
+ int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+ float dp = px / ((float) dpi / DisplayMetrics.DENSITY_DEFAULT);
+ return (int) dp;
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
new file mode 100644
index 000000000000..f58332198696
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Handler
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.statusbar.IStatusBarService
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
+import com.android.wm.shell.bubbles.properties.BubbleProperties
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.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.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for loading / inflating views & icons for a bubble.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class BubbleViewInfoTest : ShellTestCase() {
+
+ private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var bubble: Bubble
+
+ private lateinit var bubbleController: BubbleController
+ private lateinit var mainExecutor: ShellExecutor
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var bubbleBarLayerView: BubbleBarLayerView
+
+ @Before
+ fun setup() {
+ metadataFlagListener = Bubbles.BubbleMetadataFlagListener {}
+ iconFactory = BubbleIconFactory(context,
+ 60,
+ 30,
+ Color.RED,
+ mContext.resources.getDimensionPixelSize(
+ R.dimen.importance_ring_stroke_width))
+
+ mainExecutor = TestShellExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ val shellInit = ShellInit(mainExecutor)
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mainExecutor)
+ val bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleData = BubbleData(context, mock<BubbleLogger>(), bubblePositioner,
+ BubbleEducationController(context), mainExecutor)
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ bubbleController = BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ mock<BubbleDataRepository>(),
+ mock<IStatusBarService>(),
+ windowManager,
+ WindowManagerShellWrapper(mainExecutor),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ mock<BubbleLogger>(),
+ mock<TaskStackListenerImpl>(),
+ ShellTaskOrganizer(mainExecutor),
+ bubblePositioner,
+ mock<DisplayController>(),
+ null,
+ null,
+ mainExecutor,
+ mock<Handler>(),
+ mock<ShellExecutor>(),
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ mock<SyncTransactionQueue>(),
+ mock<IWindowManager>(),
+ mock<BubbleProperties>())
+
+ bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
+ surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
+ }
+
+ @Test
+ fun testPopulate() {
+ bubble = createBubbleWithShortcut()
+ val info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
+ bubbleController, bubbleStackView, iconFactory, bubble, false /* skipInflation */)
+ assertThat(info!!).isNotNull()
+
+ assertThat(info.imageView).isNotNull()
+ assertThat(info.expandedView).isNotNull()
+ assertThat(info.bubbleBarExpandedView).isNull()
+
+ assertThat(info.shortcutInfo).isNotNull()
+ assertThat(info.appName).isNotEmpty()
+ assertThat(info.rawBadgeBitmap).isNotNull()
+ assertThat(info.dotPath).isNotNull()
+ assertThat(info.bubbleBitmap).isNotNull()
+ assertThat(info.badgeBitmap).isNotNull()
+ }
+
+ @Test
+ fun testPopulateForBubbleBar() {
+ bubble = createBubbleWithShortcut()
+ val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
+ bubbleController, bubbleBarLayerView, iconFactory, bubble,
+ false /* skipInflation */)
+ assertThat(info!!).isNotNull()
+
+ assertThat(info.imageView).isNull()
+ assertThat(info.expandedView).isNull()
+ assertThat(info.bubbleBarExpandedView).isNotNull()
+
+ assertThat(info.shortcutInfo).isNotNull()
+ assertThat(info.appName).isNotEmpty()
+ assertThat(info.rawBadgeBitmap).isNotNull()
+ assertThat(info.dotPath).isNotNull()
+ assertThat(info.bubbleBitmap).isNotNull()
+ assertThat(info.badgeBitmap).isNotNull()
+ }
+
+ @Test
+ fun testPopulate_invalidShortcutIcon() {
+ bubble = createBubbleWithShortcut()
+
+ // This eventually calls down to load the shortcut icon from the app, simulate an
+ // exception here if the app has an issue loading the shortcut icon; we default to
+ // the app icon in that case / none of the icons will be null.
+ val mockIconFactory = mock<BubbleIconFactory>()
+ whenever(mockIconFactory.getBubbleDrawable(eq(context), eq(bubble.shortcutInfo),
+ any())).doThrow(RuntimeException())
+
+ val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(context,
+ bubbleController, bubbleBarLayerView, iconFactory, bubble,
+ true /* skipInflation */)
+ assertThat(info).isNotNull()
+
+ assertThat(info?.shortcutInfo).isNotNull()
+ assertThat(info?.appName).isNotEmpty()
+ assertThat(info?.rawBadgeBitmap).isNotNull()
+ assertThat(info?.dotPath).isNotNull()
+ assertThat(info?.bubbleBitmap).isNotNull()
+ assertThat(info?.badgeBitmap).isNotNull()
+ }
+
+ private fun createBubbleWithShortcut(): Bubble {
+ val shortcutInfo = ShortcutInfo.Builder(mContext, "mockShortcutId").build()
+ return Bubble("mockKey", shortcutInfo, 1000, Resources.ID_NULL,
+ "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
+ mainExecutor, metadataFlagListener)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
new file mode 100644
index 000000000000..f8eb50b978a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.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 android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.transition.TransitionInfoBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests of {@link BubblesTransitionObserver}.
+ */
+@SmallTest
+public class BubblesTransitionObserverTest {
+
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private BubbleData mBubbleData;
+
+ @Mock
+ private IBinder mTransition;
+ @Mock
+ private SurfaceControl.Transaction mStartT;
+ @Mock
+ private SurfaceControl.Transaction mFinishT;
+
+ @Mock
+ private Bubble mBubble;
+
+ private BubblesTransitionObserver mTransitionObserver;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData);
+ }
+
+ @Test
+ public void testOnTransitionReady_open_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_toFront_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskInfo_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Null task info
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */);
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskId_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Invalid task id
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT,
+ createTaskInfo(INVALID_TASK_ID));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_notOpening_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Transits that aren't opening
+ TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackAnimating_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackNotExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noSelectedBubble_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_openingMatchesExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // What's moving to front is same as the opened bubble
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ return taskInfo;
+ }
+
+ private TransitionInfo createTransitionInfo(int changeType,
+ ActivityManager.RunningTaskInfo info) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)),
+ mock(SurfaceControl.class));
+ change.setMode(changeType);
+ change.setTaskInfo(info);
+
+ return new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change).build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 9f0d89bc3128..52375850b9a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -27,15 +27,13 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -66,7 +64,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
@Before
fun setup() {
- launcherApps = mock(LauncherApps::class.java)
+ launcherApps = mock<LauncherApps>()
repository = BubbleVolatileRepository(launcherApps)
}
@@ -98,7 +96,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
repository.addBubbles(user11.identifier, listOf(bubble12))
assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier))
- Mockito.verifyNoMoreInteractions(launcherApps)
+ verifyNoMoreInteractions(launcherApps)
}
@Test
@@ -167,9 +165,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -184,9 +181,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -200,9 +196,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble2, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -219,9 +214,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
user11.identifier))
assertThat(ret).isFalse() // bubbles were NOT removed
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -237,9 +231,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
@@ -261,9 +254,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
// bubble2 is the work profile bubble and should be removed
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
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 fe2da5dd19fc..56d0f8e13f08 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
@@ -19,6 +19,10 @@ 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.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -130,7 +134,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testSetDivideRatio() {
mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
- mSplitLayout.setDivideRatio(0.5f);
+ mSplitLayout.setDivideRatio(SNAP_TO_50_50);
assertThat(mSplitLayout.getDividePosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@@ -146,7 +150,7 @@ public class SplitLayoutTests extends ShellTestCase {
public void testSnapToDismissStart() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+ SNAP_TO_START_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
@@ -158,7 +162,7 @@ public class SplitLayoutTests extends ShellTestCase {
public void testSnapToDismissEnd() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+ SNAP_TO_END_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
new file mode 100644
index 000000000000..fe261107d65b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenConstantsTest {
+
+ /**
+ * Ensures that some important constants are not changed from their set values. These values
+ * are persisted in user-defined app pairs, and changing them will break things.
+ */
+ @Test
+ fun shouldKeepExistingConstantValues() {
+ assertEquals(
+ "the value of SPLIT_POSITION_TOP_OR_LEFT should be 0",
+ 0,
+ SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT,
+ )
+ assertEquals(
+ "the value of SPLIT_POSITION_BOTTOM_OR_RIGHT should be 1",
+ 1,
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ )
+ assertEquals(
+ "the value of SNAP_TO_30_70 should be 0",
+ 0,
+ SplitScreenConstants.SNAP_TO_30_70,
+ )
+ assertEquals(
+ "the value of SNAP_TO_50_50 should be 1",
+ 1,
+ SplitScreenConstants.SNAP_TO_50_50,
+ )
+ assertEquals(
+ "the value of SNAP_TO_70_30 should be 2",
+ 2,
+ SplitScreenConstants.SNAP_TO_70_30,
+ )
+ }
+} \ No newline at end of file
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
deleted file mode 100644
index 605a762a395f..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ /dev/null
@@ -1,531 +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.desktopmode;
-
-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.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_NONE;
-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.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-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.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
-import android.window.WindowContainerTransaction.HierarchyOp;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.wm.shell.MockToken;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-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;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-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 {
-
- private static final int SECOND_DISPLAY = 2;
-
- @Mock
- private ShellController mShellController;
- @Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
- private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- @Mock
- private ShellExecutor mTestExecutor;
- @Mock
- private Handler mMockHandler;
- @Mock
- private Transitions mTransitions;
- private DesktopModeController mController;
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
- private ShellInit mShellInit;
- private StaticMockitoSession mMockitoSession;
-
- @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));
-
- mDesktopModeTaskRepository = new DesktopModeTaskRepository();
-
- mController = createController();
-
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
-
- mShellInit.init();
- clearInvocations(mShellTaskOrganizer);
- clearInvocations(mRootTaskDisplayAreaOrganizer);
- clearInvocations(mTransitions);
- }
-
- @After
- public void tearDown() {
- mMockitoSession.finishMocking();
- }
-
- @Test
- public void instantiate_addInitCallback() {
- verify(mShellInit).addInitCallback(any(), any());
- }
-
- @Test
- 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();
-
- 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);
- }
-
- @Test
- public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
- DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
- 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);
- }
-
- @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)));
-
- 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_windowingModeAndBoundsCleared() {
- createMockDisplayArea();
- RunningTaskInfo freeformTask = createFreeformTask();
- RunningTaskInfo fullscreenTask = createFullscreenTask();
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
- 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);
- }
-
- @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_allAppsInvisible_bringsToFront() {
- // Set up two active tasks on desktop, task2 is on top of task1.
- RunningTaskInfo freeformTask1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
- RunningTaskInfo freeformTask2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, 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(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
-
- // 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(freeformTask1.token.asBinder());
-
- // 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(freeformTask2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
- // 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(task1.token.asBinder());
-
- // 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(task2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_someAppsInvisible_reordersAll() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- 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 testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
- taskDefaultDisplay);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
- taskSecondDisplay);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- assertThat(wct.getHierarchyOps()).hasSize(1);
- HierarchyOp op = wct.getHierarchyOps().get(0);
- assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
- }
-
- @Test
- public void testGetVisibleTaskCount_noTasks_returnsZero() {
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- false /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
- taskDefaultDisplay.taskId,
- true /* visible */);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
- taskSecondDisplay.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
- }
-
- @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();
- }
-
- @Test
- public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
- verifyZeroInteractions(mTransitions);
- }
-
- private DesktopModeController createController() {
- return new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
- 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();
- }
-
- private WindowContainerTransaction getBringAppsToFrontTransaction() {
- final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_NONE), 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/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5d87cf8b25a6..ebcb6407a6fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -28,10 +28,10 @@ import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
-import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DisplayAreaInfo
+import android.window.RemoteTransition
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -43,6 +43,7 @@ import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
@@ -52,11 +53,17 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -67,6 +74,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
@@ -94,11 +102,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
+ @Mock lateinit var splitScreenController: SplitScreenController
+ @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+ private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private val shellExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
@@ -107,7 +118,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Before
fun setUp() {
mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
shellInit = Mockito.spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
@@ -116,8 +127,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
controller = createController()
+ controller.splitScreenController = splitScreenController
shellInit.init()
+
+ val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
+ recentsTransitionStateListener = captor.value
}
private fun createController(): DesktopTasksController {
@@ -136,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
mToggleResizeDesktopTaskTransitionHandler,
desktopModeTaskRepository,
launchAdjacentController,
+ recentsTransitionHandler,
shellExecutor
)
}
@@ -154,7 +171,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
clearInvocations(shellInit)
createController()
@@ -170,9 +187,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
markTaskHidden(task1)
markTaskHidden(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -188,9 +206,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
markTaskVisible(task1)
markTaskVisible(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -206,9 +225,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
markTaskHidden(task1)
markTaskVisible(task2)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -220,9 +240,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
val homeTask = setUpHomeTask()
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, homeTask)
}
@@ -236,9 +257,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
markTaskHidden(taskDefaultDisplay)
markTaskHidden(taskSecondDisplay)
- controller.showDesktopApps(DEFAULT_DISPLAY)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
- val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
assertThat(wct.hierarchyOps).hasSize(2)
// Expect order to be from bottom: home, task
wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
@@ -341,11 +363,35 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveToDesktop_splitTaskExitsSplit() {
+ val task = setUpSplitScreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
+ fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
+ val task = setUpFullscreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToFullscreen(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ val wct = getLatestWct(type = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
}
@@ -355,7 +401,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
controller.moveToFullscreen(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ val wct = getLatestWct(type = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@@ -373,7 +419,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.moveToFullscreen(taskDefaultDisplay)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
}
@@ -386,7 +432,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.moveTaskToFront(task1)
- val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, task1)
}
@@ -411,7 +457,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -433,7 +479,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
@@ -629,6 +675,20 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun handleRequest_recentsAnimationRunning_returnNull() {
+ // Set up a visible freeform task so a fullscreen task should be converted to freeform
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onAnimationStateChanged(true)
+
+ // Open a fullscreen task, check that it does not result in a WCT with changes to it
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
fun stashDesktopApps_stateUpdates() {
whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
@@ -695,6 +755,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
return task
}
+ private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createSplitScreenTask(displayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
private fun markTaskVisible(task: RunningTaskInfo) {
desktopModeTaskRepository.updateVisibleFreeformTasks(
task.displayId,
@@ -712,11 +779,16 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
private fun getLatestWct(
- @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ handlerClass: Class<out TransitionHandler>? = null
): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
+ if (handlerClass == null) {
+ verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+ } else {
+ verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
+ }
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
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
index 29a757c19d98..2f6f3207137d 100644
--- 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
@@ -21,6 +21,7 @@ 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.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -45,12 +46,25 @@ class DesktopTestHelpers {
@JvmOverloads
fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+ @JvmStatic
+ @JvmOverloads
+ fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setLastActiveTime(100)
+ .build()
}
/** Create a new home task */
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 69f664a3a89d..499e339bc682 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -37,8 +37,8 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index d34e27b57071..db98abbbcbf1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
+import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -60,6 +61,9 @@ public class PipBoundsStateTest extends ShellTestCase {
/** The minimum possible size of the override min size's width or height */
private static final int OVERRIDABLE_MIN_SIZE = 40;
+ /** The margin of error for floating point results. */
+ private static final float MARGIN_OF_ERROR = 0.05f;
+
private PipBoundsState mPipBoundsState;
private SizeSpecSource mSizeSpecSource;
private ComponentName mTestComponentName1;
@@ -88,6 +92,27 @@ public class PipBoundsStateTest extends ShellTestCase {
}
@Test
+ public void testBoundsScale() {
+ mPipBoundsState.setMaxSize(300, 300);
+ mPipBoundsState.setBounds(new Rect(0, 0, 100, 100));
+
+ final int currentWidth = mPipBoundsState.getBounds().width();
+ final Point maxSize = mPipBoundsState.getMaxSize();
+ final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f);
+
+ // test for currentWidth < maxWidth
+ assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR);
+
+ // reset the bounds to be at the maximum size spec
+ mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y));
+ assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+
+ // reset the bounds to be over the maximum size spec
+ mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2));
+ assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f);
+ }
+
+ @Test
public void testSetReentryState() {
final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
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 6777a5bd8ceb..9719ba89b4bb 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
@@ -28,11 +28,13 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -98,6 +100,9 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(R.bool.config_pipEnablePinchResize, true);
+
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index c40cd4069cab..45f6c8c7f69f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -23,7 +23,9 @@ import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
-import static org.junit.Assert.assertTrue;
+import static java.util.Collections.EMPTY_LIST;
+
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,7 +36,6 @@ import android.graphics.drawable.Icon;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Log;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipMediaController;
@@ -46,7 +47,9 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Unit tests for {@link TvPipActionsProvider}
@@ -69,35 +72,38 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Mock
private PendingIntent mMockPendingIntent;
- private RemoteAction createRemoteAction(int identifier) {
+ private int mNumberOfRemoteActionsCreated = 0;
+
+ private RemoteAction createRemoteAction() {
+ final int identifier = mNumberOfRemoteActionsCreated++;
return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
}
private List<RemoteAction> createRemoteActions(int numberOfActions) {
List<RemoteAction> actions = new ArrayList<>();
for (int i = 0; i < numberOfActions; i++) {
- actions.add(createRemoteAction(i));
+ actions.add(createRemoteAction());
}
return actions;
}
- private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
- for (int i = 0; i < actions.size(); i++) {
- int type = actions.get(i).getActionType();
- if (type != actionTypes[i]) {
- Log.e(TAG, "Action at index " + i + ": found " + type
- + ", expected " + actionTypes[i]);
- return false;
- }
- }
- return true;
+ private void assertActionTypes(List<Integer> expected, List<Integer> actual) {
+ assertEquals(getActionTypesStrings(expected), getActionTypesStrings(actual));
+ }
+
+ private static List<String> getActionTypesStrings(List<Integer> actionTypes) {
+ return actionTypes.stream().map(a -> TvPipAction.getActionTypeString(a))
+ .collect(Collectors.toList());
+ }
+
+ private List<Integer> getActionsTypes() {
+ return mActionsProvider.getActionsList().stream().map(a -> a.getActionType())
+ .collect(Collectors.toList());
}
@Before
public void setUp() {
- if (!isTelevision()) {
- return;
- }
+ assumeTelevision();
MockitoAnnotations.initMocks(this);
mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
mMockSystemActionsHandler);
@@ -105,57 +111,51 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void defaultSystemActions_regularPip() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
}
@Test
public void defaultSystemActions_expandedPip() {
- assumeTelevision();
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
}
@Test
public void expandedPip_enableExpansion_enable() {
- assumeTelevision();
// PiP has expanded PiP disabled.
- mActionsProvider.updateExpansionEnabled(false);
-
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
}
@Test
public void expandedPip_enableExpansion_disable() {
- assumeTelevision();
mActionsProvider.updateExpansionEnabled(true);
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(false);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
}
@Test
public void expandedPip_enableExpansion_AlreadyEnabled() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(true);
-
mActionsProvider.addListener(mMockListener);
mActionsProvider.updateExpansionEnabled(true);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
}
private void check_expandedPip_updateExpansionState(
@@ -167,8 +167,9 @@ public class TvPipActionProviderTest extends ShellTestCase {
mActionsProvider.addListener(mMockListener);
mActionsProvider.updatePipExpansionState(endExpansion);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+ getActionsTypes());
if (updateExpected) {
verify(mMockListener).onActionsChanged(0, 1, 3);
@@ -180,7 +181,6 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void expandedPip_toggleExpansion_collapse() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ true,
/* endExpansion= */ false,
@@ -189,7 +189,6 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void expandedPip_toggleExpansion_expand() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ false,
/* endExpansion= */ true,
@@ -198,7 +197,6 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void expandedPiP_updateExpansionState_alreadyExpanded() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ true,
/* endExpansion= */ true,
@@ -207,7 +205,6 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void expandedPiP_updateExpansionState_alreadyCollapsed() {
- assumeTelevision();
check_expandedPip_updateExpansionState(
/* startExpansion= */ false,
/* endExpansion= */ false,
@@ -216,8 +213,6 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void regularPiP_updateExpansionState_setCollapsed() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.updatePipExpansionState(/* expanded= */ false);
mActionsProvider.addListener(mMockListener);
@@ -229,153 +224,207 @@ public class TvPipActionProviderTest extends ShellTestCase {
@Test
public void customActions_added() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(createRemoteActions(2), null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void customActions_replacedMore() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(createRemoteActions(3), null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_CUSTOM, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
}
@Test
public void customActions_replacedLess() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), null);
+ mActionsProvider.setAppActions(EMPTY_LIST, null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void customCloseAdded() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = new ArrayList<>();
mActionsProvider.setAppActions(customActions, null);
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+ mActionsProvider.setAppActions(customActions, createRemoteAction());
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
}
@Test
public void customClose_matchesOtherCustomAction() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = createRemoteActions(2);
- RemoteAction customClose = createRemoteAction(/* id= */ 10);
+ RemoteAction customClose = createRemoteAction();
customActions.add(customClose);
mActionsProvider.addListener(mMockListener);
mActionsProvider.setAppActions(customActions, customClose);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
}
@Test
public void mediaActions_added_whileCustomActionsExist() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.setAppActions(createRemoteActions(2), null);
mActionsProvider.addListener(mMockListener);
mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
}
@Test
public void customActions_removed_whileMediaActionsExist() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
mActionsProvider.setAppActions(createRemoteActions(3), null);
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
+
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), null);
+ mActionsProvider.setAppActions(EMPTY_LIST, null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
}
@Test
public void customCloseOnly_mediaActionsShowing() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
mActionsProvider.addListener(mMockListener);
- mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+ mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
}
@Test
public void customActions_showDisabledActions() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
-
List<RemoteAction> customActions = createRemoteActions(2);
customActions.get(0).setEnabled(false);
mActionsProvider.setAppActions(customActions, null);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
- ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
}
@Test
public void mediaActions_hideDisabledActions() {
- assumeTelevision();
- mActionsProvider.updateExpansionEnabled(false);
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.onMediaActionsChanged(customActions);
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_mediaActions() {
List<RemoteAction> customActions = createRemoteActions(2);
customActions.get(0).setEnabled(false);
mActionsProvider.onMediaActionsChanged(customActions);
- assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
- new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_customActions() {
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.setAppActions(customActions, null);
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_customClose() {
+ mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
+ }
+
+ @Test
+ public void reset_All() {
+ mActionsProvider.setAppActions(createRemoteActions(2), createRemoteAction());
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE),
+ getActionsTypes());
+
+ mActionsProvider.reset();
+ assertActionTypes(
+ Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+ getActionsTypes());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 82fe5f2f6afc..974539f23b80 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -53,9 +53,8 @@ public class TvPipGravityTest extends ShellTestCase {
@Before
public void setUp() {
- if (!isTelevision()) {
- return;
- }
+ assumeTelevision();
+
MockitoAnnotations.initMocks(this);
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
@@ -101,20 +100,22 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void regularPip_defaultGravity() {
- assumeTelevision();
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
}
@Test
+ public void regularPip_defaultTvPipGravity() {
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
public void regularPip_defaultGravity_RTL() {
- assumeTelevision();
setRTL(true);
checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
}
@Test
public void updateGravity_expand_vertical() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -130,7 +131,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_expand_horizontal() {
- assumeTelevision();
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
@@ -146,7 +146,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_collapse() {
- assumeTelevision();
// Vertical expansion
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
@@ -164,7 +163,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_collapse_RTL() {
- assumeTelevision();
setRTL(true);
// Horizontal expansion
@@ -177,7 +175,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_expand_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
@@ -197,7 +194,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_expand_move_collapse() {
- assumeTelevision();
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
@@ -230,7 +226,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_regular_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
// clockwise
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
@@ -246,7 +241,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_valid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -264,7 +258,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_regular_invalid() {
- assumeTelevision();
int gravity = Gravity.BOTTOM | Gravity.RIGHT;
mTvPipBoundsState.setTvPipGravity(gravity);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
@@ -288,7 +281,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_invalid() {
- assumeTelevision();
mTvPipBoundsState.setTvPipExpanded(true);
// Vertical expanded PiP.
@@ -318,7 +310,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void previousCollapsedGravity_defaultValue() {
- assumeTelevision();
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
mTvPipBoundsState.getDefaultGravity());
setRTL(true);
@@ -328,7 +319,6 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void previousCollapsedGravity_changes_on_RTL() {
- assumeTelevision();
mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
setRTL(true);
assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
index 3a08d32bc430..e26dc7c10989 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -25,18 +25,24 @@ import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnWindowFocusChangeListener;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.SystemWindows;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,28 +56,38 @@ public class TvPipMenuControllerTest extends ShellTestCase {
@Mock
private SystemWindows mMockSystemWindows;
@Mock
- private SurfaceControl mMockPipLeash;
- @Mock
- private Handler mMockHandler;
- @Mock
- private TvPipActionsProvider mMockActionsProvider;
- @Mock
private TvPipMenuView mMockTvPipMenuView;
@Mock
private TvPipBackgroundView mMockTvPipBackgroundView;
+ private Handler mMainHandler;
private TvPipMenuController mTvPipMenuController;
+ private OnWindowFocusChangeListener mFocusChangeListener;
@Before
public void setUp() {
assumeTrue(isTelevision());
MockitoAnnotations.initMocks(this);
+ mMainHandler = new Handler(Looper.getMainLooper());
+
+ final ViewTreeObserver mockMenuTreeObserver = mock(ViewTreeObserver.class);
+ doReturn(mockMenuTreeObserver).when(mMockTvPipMenuView).getViewTreeObserver();
mTvPipMenuController = new TestTvPipMenuController();
mTvPipMenuController.setDelegate(mMockDelegate);
- mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
- mTvPipMenuController.attach(mMockPipLeash);
+ mTvPipMenuController.setTvPipActionsProvider(mock(TvPipActionsProvider.class));
+ mTvPipMenuController.attach(mock(SurfaceControl.class));
+ mFocusChangeListener = captureFocusChangeListener(mockMenuTreeObserver);
+ }
+
+ private OnWindowFocusChangeListener captureFocusChangeListener(
+ ViewTreeObserver mockTreeObserver) {
+ final ArgumentCaptor<OnWindowFocusChangeListener> focusChangeListenerCaptor =
+ ArgumentCaptor.forClass(OnWindowFocusChangeListener.class);
+ verify(mockTreeObserver).addOnWindowFocusChangeListener(
+ focusChangeListenerCaptor.capture());
+ return focusChangeListenerCaptor.getValue();
}
@Test
@@ -81,24 +97,25 @@ public class TvPipMenuControllerTest extends ShellTestCase {
@Test
public void testSwitch_FromNoMenuMode_ToMoveMode() {
- showAndAssertMoveMenu();
+ showAndAssertMoveMenu(true);
}
@Test
public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
- showAndAssertAllActionsMenu();
+ showAndAssertAllActionsMenu(true);
}
@Test
public void testSwitch_FromMoveMode_ToAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu(true);
+ showAndAssertAllActionsMenu(false);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testSwitch_FromAllActionsMode_ToMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
}
@Test
@@ -110,187 +127,282 @@ public class TvPipMenuControllerTest extends ShellTestCase {
@Test
public void testCloseMenu_MoveMode() {
- showAndAssertMoveMenu();
+ showAndAssertMoveMenu(true);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testCloseMenu_AllActionsMode() {
- showAndAssertAllActionsMenu();
+ showAndAssertAllActionsMenu(true);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
+ }
+
+ @Test
+ public void testCloseMenu_MoveModeFollowedByMoveMode() {
+ showAndAssertMoveMenu(true);
+ showAndAssertMoveMenu(false);
+
+ closeMenuAndAssertMenuClosed(true);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu(true);
+ showAndAssertAllActionsMenu(false);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
}
@Test
public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
- closeMenuAndAssertMenuClosed();
+ closeMenuAndAssertMenuClosed(true);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_NoMenuMode() {
- mTvPipMenuController.onExitMoveMode();
+ public void testCloseMenu_AllActionsModeFollowedByAllActionsMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertAllActionsMenu(false);
+
+ closeMenuAndAssertMenuClosed(true);
+ verify(mMockDelegate, never()).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMenuMode_NoMenuMode() {
+ mTvPipMenuController.onExitCurrentMenuMode();
assertMenuIsOpen(false);
verify(mMockDelegate, never()).onMenuClosed();
+ verify(mMockDelegate, never()).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_MoveMode() {
- showAndAssertMoveMenu();
+ public void testExitMenuMode_MoveMode() {
+ showAndAssertMoveMenu(true);
- mTvPipMenuController.onExitMoveMode();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
assertMenuClosed();
verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testExitMoveMode_AllActionsMode() {
- showAndAssertAllActionsMenu();
-
- mTvPipMenuController.onExitMoveMode();
- assertMenuIsInAllActionsMode();
+ public void testExitMenuMode_AllActionsMode() {
+ showAndAssertAllActionsMenu(true);
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
}
@Test
- public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ public void testExitMenuMode_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertMoveMenu(false);
- mTvPipMenuController.onExitMoveMode();
- assertMenuIsInAllActionsMode();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ assertSwitchedToAllActionsMode(2);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
- verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
- }
- @Test
- public void testOnBackPress_NoMenuMode() {
- mTvPipMenuController.onBackPress();
- assertMenuIsOpen(false);
- verify(mMockDelegate, never()).onMenuClosed();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
}
@Test
- public void testOnBackPress_MoveMode() {
- showAndAssertMoveMenu();
+ public void testExitMenuMode_AllActionsModeFollowedByAllActionsMode() {
+ showAndAssertAllActionsMenu(true);
+ showAndAssertAllActionsMenu(false);
- pressBackAndAssertMenuClosed();
- verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+ verify(mMockDelegate, never()).onInMoveModeChanged();
}
@Test
- public void testOnBackPress_AllActionsMode() {
- showAndAssertAllActionsMenu();
-
- pressBackAndAssertMenuClosed();
- }
+ public void testExitMenuMode_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu(true);
- @Test
- public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
- showAndAssertMoveMenu();
- showAndAssertAllActionsMenu();
+ showAndAssertAllActionsMenu(false);
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- pressBackAndAssertMenuClosed();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
}
@Test
- public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
- showAndAssertAllActionsMenu();
- showAndAssertMoveMenu();
+ public void testExitMenuMode_MoveModeFollowedByMoveMode() {
+ showAndAssertMoveMenu(true);
+ showAndAssertMoveMenu(false);
- mTvPipMenuController.onBackPress();
- assertMenuIsInAllActionsMode();
+ mTvPipMenuController.onExitCurrentMenuMode();
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
verify(mMockDelegate, times(2)).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
- verify(mMockTvPipBackgroundView, times(2)).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
-
- pressBackAndAssertMenuClosed();
}
@Test
public void testOnPipMovement_NoMenuMode() {
- assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ moveAndAssertMoveSuccessful(false);
}
@Test
public void testOnPipMovement_MoveMode() {
- showAndAssertMoveMenu();
- assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
- verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+ showAndAssertMoveMenu(true);
+ moveAndAssertMoveSuccessful(true);
}
@Test
public void testOnPipMovement_AllActionsMode() {
- showAndAssertAllActionsMenu();
- assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ showAndAssertAllActionsMenu(true);
+ moveAndAssertMoveSuccessful(false);
}
@Test
- public void testOnPipWindowFocusChanged_NoMenuMode() {
- mTvPipMenuController.onPipWindowFocusChanged(false);
- assertMenuIsOpen(false);
+ public void testUnexpectedFocusChanges() {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
+
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed();
+
+ showAndAssertMoveMenu(true);
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed(2);
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
}
@Test
- public void testOnPipWindowFocusChanged_MoveMode() {
- showAndAssertMoveMenu();
- mTvPipMenuController.onPipWindowFocusChanged(false);
- assertMenuClosed();
+ public void testAsyncScenario_AllActionsModeRequestFollowedByAsyncMoveModeRequest() {
+ mTvPipMenuController.showMenu();
+ // Artificially delaying the focus change update and adding a move request to simulate an
+ // async problematic situation.
+ mTvPipMenuController.showMovementMenu();
+ // The first focus change update arrives
+ mFocusChangeListener.onWindowFocusChanged(true);
+
+ // We expect that the TvPipMenuController will directly switch to the "pending" menu mode
+ // - MODE_MOVE_MENU, because no change of focus is needed.
+ assertSwitchedToMoveMode();
+ }
+
+ @Test
+ public void testAsyncScenario_MoveModeRequestFollowedByAsyncAllActionsModeRequest() {
+ mTvPipMenuController.showMovementMenu();
+ mTvPipMenuController.showMenu();
+
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
+ verify(mMockDelegate, never()).onInMoveModeChanged();
}
@Test
- public void testOnPipWindowFocusChanged_AllActionsMode() {
- showAndAssertAllActionsMenu();
- mTvPipMenuController.onPipWindowFocusChanged(false);
+ public void testAsyncScenario_DropObsoleteIntermediateModeSwitchRequests() {
+ mTvPipMenuController.showMovementMenu();
+ mTvPipMenuController.closeMenu();
+
+ // Focus change from showMovementMenu() call.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToMoveMode();
+ verify(mMockDelegate).onInMoveModeChanged();
+
+ // Focus change from closeMenu() call.
+ mFocusChangeListener.onWindowFocusChanged(false);
assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ // Unexpected focus gain should open MODE_ALL_ACTIONS_MENU.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(1);
+
+ mTvPipMenuController.closeMenu();
+ mTvPipMenuController.showMovementMenu();
+
+ assertSwitchedToMoveMode(2);
+
+ mFocusChangeListener.onWindowFocusChanged(false);
+ assertMenuClosed(2);
+
+ // Closing the menu resets the default menu mode, so the next focus gain opens the menu in
+ // the default mode - MODE_ALL_ACTIONS_MENU.
+ mFocusChangeListener.onWindowFocusChanged(true);
+ assertSwitchedToAllActionsMode(2);
+ verify(mMockDelegate, times(4)).onInMoveModeChanged();
+
}
- private void showAndAssertMoveMenu() {
+ private void showAndAssertMoveMenu(boolean focusChange) {
mTvPipMenuController.showMovementMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ }
+ assertSwitchedToMoveMode();
+ }
+
+ private void assertSwitchedToMoveMode() {
+ assertSwitchedToMoveMode(1);
+ }
+
+ private void assertSwitchedToMoveMode(int times) {
assertMenuIsInMoveMode();
- verify(mMockDelegate).onInMoveModeChanged();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ verify(mMockDelegate, times(2 * times - 1)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_MOVE_MENU));
+ }
+
+ private void showAndAssertAllActionsMenu(boolean focusChange) {
+ showAndAssertAllActionsMenu(focusChange, 1);
}
- private void showAndAssertAllActionsMenu() {
+ private void showAndAssertAllActionsMenu(boolean focusChange, int times) {
mTvPipMenuController.showMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(true);
+ }
+
+ assertSwitchedToAllActionsMode(times);
+ }
+
+ private void assertSwitchedToAllActionsMode(int times) {
assertMenuIsInAllActionsMode();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ verify(mMockTvPipMenuView, times(times))
+ .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
+ verify(mMockTvPipBackgroundView, times(times))
+ .transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU));
}
- private void closeMenuAndAssertMenuClosed() {
+ private void closeMenuAndAssertMenuClosed(boolean focusChange) {
mTvPipMenuController.closeMenu();
+ if (focusChange) {
+ mFocusChangeListener.onWindowFocusChanged(false);
+ }
assertMenuClosed();
}
- private void pressBackAndAssertMenuClosed() {
- mTvPipMenuController.onBackPress();
- assertMenuClosed();
+ private void moveAndAssertMoveSuccessful(boolean expectedSuccess) {
+ mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE);
+ verify(mMockDelegate, times(expectedSuccess ? 1 : 0)).movePip(eq(TEST_MOVE_KEYCODE));
}
private void assertMenuClosed() {
+ assertMenuClosed(1);
+ }
+
+ private void assertMenuClosed(int times) {
assertMenuIsOpen(false);
- verify(mMockDelegate).onMenuClosed();
- verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
- verify(mMockTvPipBackgroundView).transitionToMenuMode(eq(MODE_NO_MENU));
+ verify(mMockDelegate, times(times)).onMenuClosed();
+ verify(mMockTvPipMenuView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
+ verify(mMockTvPipBackgroundView, times(times)).transitionToMenuMode(eq(MODE_NO_MENU));
}
private void assertMenuIsOpen(boolean open) {
@@ -312,15 +424,10 @@ public class TvPipMenuControllerTest extends ShellTestCase {
assertMenuIsOpen(true);
}
- private void assertPipMoveSuccessful(boolean expected, boolean actual) {
- assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
- + mTvPipMenuController.getMenuModeString(), expected == actual);
- }
-
private class TestTvPipMenuController extends TvPipMenuController {
TestTvPipMenuController() {
- super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+ super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMainHandler);
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index baa06f2f0c45..bbd65be9abda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,6 +24,7 @@ import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import com.android.wm.shell.util.GroupedRecentTaskInfo
import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
@@ -123,6 +124,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2)
assertThat(recentTaskInfoParcel.splitBounds).isNotNull()
+ assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50)
}
@Test
@@ -156,7 +158,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
- val splitBounds = SplitBounds(Rect(), Rect(), 1, 2)
+ val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50)
return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
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 2c69522413d5..10e9e11e9004 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
@@ -17,10 +17,12 @@
package com.android.wm.shell.recents;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -172,10 +174,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Verify only one update if the split info is the same
SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
}
@@ -206,8 +208,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
- SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ SplitBounds pair2Bounds =
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -235,8 +239,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
- SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ SplitBounds pair2Bounds =
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -256,7 +262,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -296,7 +302,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+ when(DesktopModeStatus.isEnabled()).thenReturn(false);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -333,7 +339,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3);
// Add a pair
- SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3);
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
@@ -367,6 +374,37 @@ public class RecentTasksControllerTest extends ShellTestCase {
verify(mRecentTasksController).notifyRecentTasksChanged();
}
+ @Test
+ public void getNullSplitBoundsNonSplitTask() {
+ SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
+ assertNull("splitBounds should be null for non-split task", sb);
+ }
+
+ @Test
+ public void getNullSplitBoundsInvalidTask() {
+ SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(INVALID_TASK_ID);
+ assertNull("splitBounds should be null for invalid taskID", sb);
+ }
+
+ @Test
+ public void getSplitBoundsForSplitTask() {
+ SplitBounds pair1Bounds = mock(SplitBounds.class);
+ SplitBounds pair2Bounds = mock(SplitBounds.class);
+
+ mRecentTasksController.addSplitPair(1, 2, pair1Bounds);
+ mRecentTasksController.addSplitPair(4, 3, pair2Bounds);
+
+ SplitBounds splitBounds2 = mRecentTasksController.getSplitBoundsForTaskId(2);
+ SplitBounds splitBounds1 = mRecentTasksController.getSplitBoundsForTaskId(1);
+ assertEquals("Different splitBounds for same pair", splitBounds1, splitBounds2);
+ assertEquals(splitBounds1, pair1Bounds);
+
+ SplitBounds splitBounds3 = mRecentTasksController.getSplitBoundsForTaskId(3);
+ SplitBounds splitBounds4 = mRecentTasksController.getSplitBoundsForTaskId(4);
+ assertEquals("Different splitBounds for same pair", splitBounds3, splitBounds4);
+ assertEquals(splitBounds4, pair2Bounds);
+ }
+
/**
* Helper to create a task with a given task id.
*/
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index 50d02ae0dccd..b790aee6fb0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -1,5 +1,7 @@
package com.android.wm.shell.recents;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -44,21 +46,21 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testVerticalStacked() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top);
@@ -69,7 +71,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testVerticalDividerBounds() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
assertEquals(0, dividerBounds.top);
@@ -80,7 +82,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testEqualVerticalTaskPercent() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
}
@@ -88,7 +90,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testEqualHorizontalTaskPercent() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
}
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 568db919818c..99cd4f391153 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -218,8 +219,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
- public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -237,6 +237,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
+ verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
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 5efd9ad97a3e..befc702b01aa 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
@@ -47,11 +47,8 @@ import static org.mockito.Mockito.spy;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.os.IBinder;
-import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -66,7 +63,6 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -77,6 +73,8 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.TestRemoteTransition;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -205,7 +203,31 @@ public class SplitTransitionTests extends ShellTestCase {
// Make sure split-screen is now visible
assertTrue(mStageCoordinator.isSplitScreenVisible());
- assertTrue(testRemote.mCalled);
+ assertTrue(testRemote.isCalled());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testRemoteTransitionConsumed() {
+ // Omit side child change
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(TRANSIT_OPEN, mMainChild)
+ .build();
+ TestRemoteTransition testRemote = new TestRemoteTransition();
+
+ IBinder transition = mSplitScreenTransitions.startEnterTransition(
+ TRANSIT_OPEN, new WindowContainerTransaction(),
+ new RemoteTransition(testRemote, "Test"), mStageCoordinator,
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ mMainStage.onTaskAppeared(mMainChild, createMockSurface());
+ boolean accepted = mStageCoordinator.startAnimation(transition, info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(Transitions.TransitionFinishCallback.class));
+ assertTrue(accepted);
+
+ assertTrue(testRemote.isConsumed());
+
}
@Test
@@ -468,24 +490,4 @@ public class SplitTransitionTests extends ShellTestCase {
return out;
}
- class TestRemoteTransition extends IRemoteTransition.Stub {
- boolean mCalled = false;
- final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
-
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction startTransaction,
- IRemoteTransitionFinishedCallback finishCallback)
- throws RemoteException {
- mCalled = true;
- finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
- }
-
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0088051928fb..4afb29ecd98c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -43,6 +43,8 @@ import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
@@ -88,6 +90,10 @@ public class TaskViewTest extends ShellTestCase {
SyncTransactionQueue mSyncQueue;
@Mock
Transitions mTransitions;
+ @Mock
+ Looper mViewLooper;
+ @Mock
+ Handler mViewHandler;
SurfaceSession mSession;
SurfaceControl mLeash;
@@ -105,6 +111,8 @@ public class TaskViewTest extends ShellTestCase {
.build();
mContext = getContext();
+ doReturn(true).when(mViewLooper).isCurrentThread();
+ doReturn(mViewLooper).when(mViewHandler).getLooper();
mTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.token = mToken;
@@ -132,6 +140,7 @@ public class TaskViewTest extends ShellTestCase {
mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
mTaskViewTransitions, mSyncQueue));
mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setHandler(mViewHandler);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -646,4 +655,17 @@ public class TaskViewTest extends ShellTestCase {
assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
}
+
+ @Test
+ public void testOnTaskInfoChangedOnSameUiThread() {
+ mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+ verify(mViewHandler, never()).post(any());
+ }
+
+ @Test
+ public void testOnTaskInfoChangedOnDifferentUiThread() {
+ doReturn(false).when(mViewLooper).isCurrentThread();
+ mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
+ verify(mViewHandler).post(any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 03ed18c86608..050443914355 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -65,12 +65,6 @@ public class TaskViewTransitionsTest extends ShellTestCase {
ActivityManager.RunningTaskInfo mTaskInfo;
@Mock
WindowContainerToken mToken;
- @Mock
- TaskViewTaskController mTaskViewTaskController2;
- @Mock
- ActivityManager.RunningTaskInfo mTaskInfo2;
- @Mock
- WindowContainerToken mToken2;
TaskViewTransitions mTaskViewTransitions;
@@ -86,16 +80,10 @@ public class TaskViewTransitionsTest extends ShellTestCase {
mTaskInfo.token = mToken;
mTaskInfo.taskId = 314;
mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
- when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
-
- mTaskInfo2 = new ActivityManager.RunningTaskInfo();
- mTaskInfo2.token = mToken2;
- mTaskInfo2.taskId = 315;
- mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class);
- when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2);
mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+ when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
}
@Test
@@ -138,7 +126,7 @@ public class TaskViewTransitionsTest extends ShellTestCase {
}
@Test
- public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() {
+ public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
@@ -154,43 +142,6 @@ public class TaskViewTransitionsTest extends ShellTestCase {
}
@Test
- public void testSetTaskBounds_taskVisibleWithPendingChange_transition() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
- // Consume the pending transition from visibility change
- TaskViewTransitions.PendingTransition pending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(pending).isNotNull();
- mTaskViewTransitions.startAnimation(pending.mClaimed,
- mock(TransitionInfo.class),
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mock(Transitions.TransitionFinishCallback.class));
- // Verify it was consumed
- TaskViewTransitions.PendingTransition checkPending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(checkPending).isNull();
-
- // Test that set bounds creates a new transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
- .isNotNull();
-
- // Test that set bounds again (with different bounds) creates another transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 300, 200));
- List<TaskViewTransitions.PendingTransition> pendingList =
- mTaskViewTransitions.findAllPending(mTaskViewTaskController)
- .stream()
- .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
- .toList();
- assertThat(pendingList.size()).isEqualTo(2);
- }
-
- @Test
public void testSetTaskBounds_sameBounds_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -217,16 +168,6 @@ public class TaskViewTransitionsTest extends ShellTestCase {
mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
assertThat(pendingBounds).isNotNull();
- // Test that setting same bounds with in-flight transition doesn't cause another one
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- List<TaskViewTransitions.PendingTransition> pendingList =
- mTaskViewTransitions.findAllPending(mTaskViewTaskController)
- .stream()
- .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
- .toList();
- assertThat(pendingList.size()).isEqualTo(1);
-
// Consume the pending bounds transaction
mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
mock(TransitionInfo.class),
@@ -246,42 +187,6 @@ public class TaskViewTransitionsTest extends ShellTestCase {
assertThat(pendingBounds2).isNull();
}
-
- @Test
- public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
- mTaskViewTransitions.addTaskView(mTaskViewTaskController2);
-
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
- // Consume the pending transition from visibility change
- TaskViewTransitions.PendingTransition pending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(pending).isNotNull();
- mTaskViewTransitions.startAnimation(pending.mClaimed,
- mock(TransitionInfo.class),
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mock(Transitions.TransitionFinishCallback.class));
- // Verify it was consumed
- TaskViewTransitions.PendingTransition checkPending =
- mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
- assertThat(checkPending).isNull();
-
- // Set the second taskview as visible & check that it has a pending transition
- mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true);
- TaskViewTransitions.PendingTransition pending2 =
- mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT);
- assertThat(pending2).isNotNull();
-
- // Test that set bounds on the first taskview will create a new transition
- mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
- new Rect(0, 0, 100, 100));
- assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
- .isNotNull();
- }
-
@Test
public void testSetTaskVisibility_taskRemoved_noNPE() {
mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
new file mode 100644
index 000000000000..7a8a2a93e0e1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration.ActivityType;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.TransitionMode;
+import android.window.WindowOrganizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+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.DisplayController;
+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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the home transition observer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HomeTransitionObserverTest extends ShellTestCase {
+
+ private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
+ private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final DisplayController mDisplayController = mock(DisplayController.class);
+
+ private Transitions mTransition;
+
+ @Before
+ public void setUp() {
+ mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
+ mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ }
+
+ @Test
+ public void testHomeActivityWithOpenModeNotifiesHomeIsVisible() throws RemoteException {
+ IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+
+ HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
+ mTransition);
+ observer.setHomeTransitionListener(listener);
+
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN);
+
+ observer.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(listener, times(1)).onHomeVisibilityChanged(true);
+ }
+
+ @Test
+ public void testHomeActivityWithCloseModeNotifiesHomeIsNotVisible() throws RemoteException {
+ IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+
+ HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
+ mTransition);
+ observer.setHomeTransitionListener(listener);
+
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_TO_BACK);
+
+ observer.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(listener, times(1)).onHomeVisibilityChanged(false);
+ }
+
+ @Test
+ public void testNonHomeActivityDoesNotTriggerCallback() throws RemoteException {
+ IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
+ when(listener.asBinder()).thenReturn(mock(IBinder.class));
+
+ HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
+ mTransition);
+ observer.setHomeTransitionListener(listener);
+
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_UNDEFINED, TRANSIT_TO_BACK);
+
+ observer.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(listener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ }
+
+ /**
+ * Helper class to initialize variables for the rest.
+ */
+ private void setupTransitionInfo(ActivityManager.RunningTaskInfo taskInfo,
+ TransitionInfo.Change change,
+ @ActivityType int activityType,
+ @TransitionMode int mode) {
+ when(taskInfo.getActivityType()).thenReturn(activityType);
+ when(change.getMode()).thenReturn(mode);
+ }
+
+}
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 b32e0d6b4b39..da83d4c0a122 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
@@ -50,6 +50,7 @@ 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.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
@@ -92,13 +93,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.TransitionInfoBuilder;
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.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -145,7 +147,9 @@ public class ShellTransitionTests extends ShellTestCase {
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor);
- verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+ // One from Transitions, one from RootTaskDisplayAreaOrganizer
+ verify(shellInit).addInitCallback(any(), eq(t));
+ verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
}
@Test
@@ -285,6 +289,10 @@ public class ShellTransitionTests extends ShellTestCase {
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
@@ -427,6 +435,10 @@ public class ShellTransitionTests extends ShellTestCase {
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
TransitionFilter filter = new TransitionFilter();
@@ -473,6 +485,10 @@ public class ShellTransitionTests extends ShellTestCase {
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
}
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ }
};
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
@@ -1061,7 +1077,8 @@ public class ShellTransitionTests extends ShellTestCase {
mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor);
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions, null);
+ new RecentsTransitionHandler(shellInit, transitions,
+ mock(RecentTasksController.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
new file mode 100644
index 000000000000..87330d2dc877
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.transition;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * {@link IRemoteTransition} for testing purposes.
+ * Stores info about
+ * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+ * IRemoteTransitionFinishedCallback)} being called.
+ */
+public class TestRemoteTransition extends IRemoteTransition.Stub {
+ private boolean mCalled = false;
+ private boolean mConsumed = false;
+ final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
+
+ @Override
+ public void startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ mCalled = true;
+ finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
+ mConsumed = true;
+ }
+
+ /**
+ * Check whether this remote transition
+ * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
+ * IRemoteTransitionFinishedCallback)} is called
+ */
+ public boolean isCalled() {
+ return mCalled;
+ }
+
+ /**
+ * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)}
+ * is called
+ */
+ public boolean isConsumed() {
+ return mConsumed;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
index a658375ca38a..834385832e4a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.transition;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 1d94c9c31de2..7917ba6eeb41 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -17,9 +17,12 @@
package com.android.wm.shell.unfold;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -27,6 +30,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.view.Display;
@@ -35,6 +39,9 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import com.android.window.flags.FakeFeatureFlagsImpl;
+import com.android.window.flags.FeatureFlags;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
@@ -45,6 +52,8 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.List;
@@ -132,6 +141,55 @@ public class UnfoldTransitionHandlerTest {
}
@Test
+ public void startAnimation_sameTransitionAsHandleRequest_startsAnimation() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isTrue();
+ }
+
+ @Test
+ public void startAnimation_differentTransitionFromRequestWithUnfold_startsAnimation() {
+ mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ createUnfoldTransitionInfo(),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isTrue();
+ }
+
+ @Test
+ public void startAnimation_differentTransitionFromRequestWithoutUnfold_doesNotStart() {
+ mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ createNonUnfoldTransitionInfo(),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ assertThat(animationStarted).isFalse();
+ }
+
+ @Test
public void startAnimation_animationFinishes_finishesTheTransition() {
TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
@@ -215,6 +273,12 @@ public class UnfoldTransitionHandlerTest {
triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
}
+ private TransitionRequestInfo createNoneTransitionInfo() {
+ return new TransitionRequestInfo(TRANSIT_NONE,
+ /* triggerTask= */ null, /* remoteTransition= */ null,
+ /* displayChange= */ null, /* flags= */ 0);
+ }
+
private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
ShellUnfoldProgressProvider.UnfoldListener {
@@ -277,4 +341,35 @@ public class UnfoldTransitionHandlerTest {
return false;
}
}
+
+ static class TestCase {
+ private final boolean mShouldHandleMixedUnfold;
+
+ public TestCase(boolean shouldHandleMixedUnfold) {
+ mShouldHandleMixedUnfold = shouldHandleMixedUnfold;
+ }
+
+ public boolean mixedUnfoldFlagEnabled() {
+ return mShouldHandleMixedUnfold;
+ }
+
+ @Override
+ public String toString() {
+ return "shouldHandleMixedUnfold flag = " + mShouldHandleMixedUnfold;
+ }
+ }
+
+ private TransitionInfo createUnfoldTransitionInfo() {
+ TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+ TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
+ change.setStartAbsBounds(new Rect(0, 0, 10, 10));
+ change.setEndAbsBounds(new Rect(0, 0, 100, 100));
+ change.setFlags(TransitionInfo.FLAG_IS_DISPLAY);
+ transitionInfo.addChange(change);
+ return transitionInfo;
+ }
+
+ private TransitionInfo createNonUnfoldTransitionInfo() {
+ return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+ }
} \ No newline at end of file
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
deleted file mode 100644
index 596d6dd3a3d2..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ /dev/null
@@ -1,318 +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.ACTIVITY_TYPE_UNDEFINED;
-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 org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-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.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
-import com.android.wm.shell.desktopmode.DesktopTasksController;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-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;
-import java.util.function.Supplier;
-
-/** Tests of {@link DesktopModeWindowDecorViewModel} */
-@SmallTest
-public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
-
- private static final String TAG = "DesktopModeWindowDecorViewModelTests";
- private static final Rect STABLE_INSETS = new Rect(0, 100, 0, 0);
-
- @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 DisplayLayout mDisplayLayout;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private DesktopModeController mDesktopModeController;
- @Mock private DesktopTasksController mDesktopTasksController;
- @Mock private InputMonitor mInputMonitor;
- @Mock private InputManager mInputManager;
- @Mock private Transitions mTransitions;
- @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
- @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
- @Mock private SurfaceControl.Transaction mTransaction;
- @Mock private Display mDisplay;
- @Mock private ShellController mShellController;
- @Mock private ShellInit mShellInit;
- @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
- mDesktopModeKeyguardChangeListener;
- private final List<InputManager> mMockInputManagers = new ArrayList<>();
-
- private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
-
- @Before
- public void setUp() {
- mMockInputManagers.add(mInputManager);
-
- mDesktopModeWindowDecorViewModel =
- new DesktopModeWindowDecorViewModel(
- mContext,
- mMainHandler,
- mMainChoreographer,
- mShellInit,
- mTaskOrganizer,
- mDisplayController,
- mShellController,
- mSyncQueue,
- mTransitions,
- Optional.of(mDesktopModeController),
- Optional.of(mDesktopTasksController),
- mDesktopModeWindowDecorFactory,
- mMockInputMonitorFactory,
- mTransactionFactory,
- mDesktopModeKeyguardChangeListener
- );
-
- doReturn(mDesktopModeWindowDecoration)
- .when(mDesktopModeWindowDecorFactory)
- .create(any(), any(), any(), any(), any(), any(), any(), any());
- doReturn(mTransaction).when(mTransactionFactory).get();
- doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt());
- doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets();
- doNothing().when(mShellController).addKeyguardChangeListener(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]);
-
- mDesktopModeWindowDecoration.mDisplay = mDisplay;
- doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
- }
-
- @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();
- }
-
- @Test
- public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception {
- doReturn(true).when(
- mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded();
-
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN);
- taskInfo.isFocused = true;
- 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, never())
- .create(any(), any(), any(), any(), any(), any(), any(), any());
- }
-
- 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, @WindowConfiguration.WindowingMode 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/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
new file mode 100644
index 000000000000..57aa47e85556
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -0,0 +1,426 @@
+/*
+ * 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.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.os.Handler
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Choreographer
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.InputChannel
+import android.view.InputMonitor
+import android.view.InsetsSource
+import android.view.InsetsState
+import android.view.SurfaceControl
+import android.view.SurfaceView
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
+import androidx.core.content.getSystemService
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.sysui.KeyguardChangeListener
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+import java.util.Optional
+import java.util.function.Supplier
+
+
+/** Tests of [DesktopModeWindowDecorViewModel] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+ @Mock private lateinit var mockDesktopModeWindowDecorFactory:
+ DesktopModeWindowDecoration.Factory
+ @Mock private lateinit var mockMainHandler: Handler
+ @Mock private lateinit var mockMainChoreographer: Choreographer
+ @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var displayInsetsController: DisplayInsetsController
+ @Mock private lateinit var mockSyncQueue: SyncTransactionQueue
+ @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
+ @Mock private lateinit var mockInputMonitor: InputMonitor
+ @Mock private lateinit var mockTransitions: Transitions
+ @Mock private lateinit var mockInputMonitorFactory:
+ DesktopModeWindowDecorViewModel.InputMonitorFactory
+ @Mock private lateinit var mockShellController: ShellController
+ @Mock private lateinit var mockShellExecutor: ShellExecutor
+ @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
+ @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
+
+ private val transactionFactory = Supplier<SurfaceControl.Transaction> {
+ SurfaceControl.Transaction()
+ }
+
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
+
+ @Before
+ fun setUp() {
+ shellInit = ShellInit(mockShellExecutor)
+ desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
+ mContext,
+ mockMainHandler,
+ mockMainChoreographer,
+ shellInit,
+ mockShellCommandHandler,
+ mockTaskOrganizer,
+ mockDisplayController,
+ mockShellController,
+ displayInsetsController,
+ mockSyncQueue,
+ mockTransitions,
+ Optional.of(mockDesktopTasksController),
+ mockRecentsTransitionHandler,
+ mockDesktopModeWindowDecorFactory,
+ mockInputMonitorFactory,
+ transactionFactory,
+ mockRootTaskDisplayAreaOrganizer
+ )
+
+ whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
+
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ val inputChannels = InputChannel.openInputChannelPair(TAG)
+ inputChannels.first().dispose()
+ whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1])
+
+ shellInit.init()
+
+ val listenerCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
+ desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
+ }
+
+ @Test
+ fun testDeleteCaptionOnChangeTransitionWhenNecessary() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val taskSurface = SurfaceControl()
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task, taskSurface)
+
+ task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
+ task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
+ onTaskChanging(task, taskSurface)
+
+ verify(mockDesktopModeWindowDecorFactory).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+ verify(decoration).close()
+ }
+
+ @Test
+ fun testCreateCaptionOnChangeTransitionWhenNecessary() {
+ val task = createTask(
+ windowingMode = WINDOWING_MODE_UNDEFINED,
+ activityType = ACTIVITY_TYPE_UNDEFINED
+ )
+ val taskSurface = SurfaceControl()
+ setUpMockDecorationForTask(task)
+
+ onTaskChanging(task, taskSurface)
+ verify(mockDesktopModeWindowDecorFactory, never()).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM)
+ task.setActivityType(ACTIVITY_TYPE_STANDARD)
+ onTaskChanging(task, taskSurface)
+ verify(mockDesktopModeWindowDecorFactory, times(1)).create(
+ mContext,
+ mockDisplayController,
+ mockTaskOrganizer,
+ task,
+ taskSurface,
+ mockMainHandler,
+ mockMainChoreographer,
+ mockSyncQueue,
+ mockRootTaskDisplayAreaOrganizer
+ )
+ }
+
+ @Test
+ fun testCreateAndDisposeEventReceiver() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+ verify(mockInputMonitorFactory).create(any(), any())
+ verify(mockInputMonitor).dispose()
+ }
+
+ @Test
+ fun testEventReceiversOnMultipleDisplays() {
+ val secondaryDisplay = createVirtualDisplay() ?: return
+ val secondaryDisplayId = secondaryDisplay.display.displayId
+ val task = createTask(displayId = DEFAULT_DISPLAY, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(
+ displayId = secondaryDisplayId,
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ val thirdTask = createTask(
+ displayId = secondaryDisplayId,
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTask)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+ secondaryDisplay.release()
+
+ verify(mockInputMonitorFactory, times(2)).create(any(), any())
+ verify(mockInputMonitor, times(1)).dispose()
+ }
+
+ @Test
+ fun testCaptionIsNotCreatedWhenKeyguardIsVisible() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>()
+ verify(mockShellController).addKeyguardChangeListener(keyguardListenerCaptor.capture())
+
+ keyguardListenerCaptor.firstValue.onKeyguardVisibilityChanged(
+ true /* visible */,
+ true /* occluded */,
+ false /* animatingDismiss */
+ )
+ onTaskOpening(task)
+
+ task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
+ task.setWindowingMode(ACTIVITY_TYPE_UNDEFINED)
+ onTaskChanging(task)
+
+ verify(mockDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testRelayoutBlockedDuringRecentsTransition() {
+ val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
+ verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
+
+ val transition = mock(IBinder::class.java)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+
+ // Make sure a window decorations exists first by launching a freeform task.
+ onTaskOpening(task)
+ // Now call back when a Recents transition starts.
+ recentsCaptor.firstValue.onTransitionStarted(transition)
+
+ verify(decoration).incrementRelayoutBlock()
+ verify(decoration).addTransitionPausingRelayout(transition)
+ }
+
+ @Test
+ fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout occurs when status bar inset visibility changes
+ verify(decoration, times(1)).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add navigation bar insets source
+ val insetsState = InsetsState()
+ val navigationBarInsetsSourceId = 1
+ val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
+ navigationBarInsetsSource.isVisible = false
+ insetsState.addSource(navigationBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout does not occur when non-status bar inset changes visibility
+ verify(decoration, never()).relayout(task)
+ }
+
+ @Test
+ fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task)
+
+ // Add status bar insets source
+ val insetsState = InsetsState()
+ val statusBarInsetsSourceId = 0
+ val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
+ statusBarInsetsSource.isVisible = false
+ insetsState.addSource(statusBarInsetsSource)
+
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+ desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
+
+ // Verify relayout runs only once when status bar inset visibility changes.
+ verify(decoration, times(1)).relayout(task)
+ }
+
+ private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ desktopModeWindowDecorViewModel.onTaskOpening(
+ task,
+ leash,
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction()
+ )
+ }
+
+ private fun onTaskChanging(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
+ desktopModeWindowDecorViewModel.onTaskChanging(
+ task,
+ leash,
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction()
+ )
+ }
+
+ private fun createTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ @WindowConfiguration.WindowingMode windowingMode: Int,
+ activityType: Int = ACTIVITY_TYPE_STANDARD,
+ focused: Boolean = true
+ ): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setWindowingMode(windowingMode)
+ .setVisible(true)
+ .setActivityType(activityType)
+ .build().apply {
+ isFocused = focused
+ }
+ }
+
+ private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
+ val decoration = mock(DesktopModeWindowDecoration::class.java)
+ whenever(mockDesktopModeWindowDecorFactory.create(
+ any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ ).thenReturn(decoration)
+ decoration.mTaskInfo = task
+ whenever(decoration.isFocused).thenReturn(task.isFocused)
+ return decoration
+ }
+
+ private fun setUpMockDecorationsForTasks(vararg tasks: RunningTaskInfo) {
+ tasks.forEach { setUpMockDecorationForTask(it) }
+ }
+
+ private fun createVirtualDisplay(): VirtualDisplay? {
+ val surfaceView = SurfaceView(mContext)
+ return mContext.getSystemService<DisplayManager>()?.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays",
+ /*width=*/ 400,
+ /*height=*/ 400,
+ /*densityDpi=*/320,
+ surfaceView.holder.surface,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ )
+ }
+
+ private fun RunningTaskInfo.setWindowingMode(@WindowConfiguration.WindowingMode mode: Int) {
+ configuration.windowConfiguration.windowingMode = mode
+ }
+
+ private fun RunningTaskInfo.setActivityType(type: Int) {
+ configuration.windowConfiguration.activityType = type
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeWindowDecorViewModelTests"
+ private val STABLE_INSETS = Rect(0, 100, 0, 0)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
new file mode 100644
index 000000000000..18fcdd00df9d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.function.Supplier;
+
+/**
+ * Tests for {@link DesktopModeWindowDecoration}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DesktopModeWindowDecorationTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeWindowDecorationTests extends ShellTestCase {
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private ShellTaskOrganizer mMockShellTaskOrganizer;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private Choreographer mMockChoreographer;
+ @Mock
+ private SyncTransactionQueue mMockSyncQueue;
+ @Mock
+ private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
+ @Mock
+ private Supplier<SurfaceControl.Transaction> mMockTransactionSupplier;
+ @Mock
+ private SurfaceControl.Transaction mMockTransaction;
+ @Mock
+ private SurfaceControl mMockSurfaceControl;
+ @Mock
+ private SurfaceControlViewHost mMockSurfaceControlViewHost;
+ @Mock
+ private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+
+ private final Configuration mConfiguration = new Configuration();
+
+ @Before
+ public void setUp() {
+ doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
+ any(), any(), any());
+ doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
+ }
+
+ @Test
+ public void testMenusClosedWhenTaskIsInvisible() {
+ doReturn(mMockTransaction).when(mMockTransaction).hide(any());
+
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(false /* visible */);
+ final DesktopModeWindowDecoration spyWindowDecor =
+ spy(createWindowDecoration(taskInfo));
+
+ spyWindowDecor.relayout(taskInfo);
+
+ // Menus should close if open before the task being invisible causes relayout to return.
+ verify(spyWindowDecor).closeHandleMenu();
+ verify(spyWindowDecor).closeMaximizeMenu();
+
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
+ mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
+ mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ SurfaceControl.Builder::new, mMockTransactionSupplier,
+ WindowContainerTransaction::new, SurfaceControl::new,
+ mMockSurfaceControlViewHostFactory);
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(visible)
+ .build();
+ taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
+ "DesktopModeWindowDecorationTests");
+ taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
+ "DesktopModeWindowDecorationTests");
+ return taskInfo;
+
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index de46b31879ed..5c0e04aecf6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -76,7 +76,7 @@ class DragPositioningCallbackUtilityTest {
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 6f0599aa8243..add78b2ee8b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@ import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -76,7 +80,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -88,7 +100,8 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -641,9 +654,81 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
}
})
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
}
companion object {
@@ -664,11 +749,17 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 3465ddd9d101..a70ebf14324a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,9 @@ import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerToken
@@ -30,6 +33,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
@@ -104,7 +115,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
- configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -361,11 +373,79 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Drag back to starting bounds.
+ performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 7fc1c99bb44e..8e42f74b8d17 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
@@ -17,12 +17,19 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,9 +43,11 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
@@ -47,6 +56,7 @@ import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
import android.view.Display;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
@@ -58,10 +68,12 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+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.common.DisplayController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import org.junit.Before;
@@ -88,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase {
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 static final int CORNER_RADIUS = 20;
+ private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -108,17 +121,19 @@ public class WindowDecorationTests extends ShellTestCase {
private WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
private SurfaceSyncGroup mMockSurfaceSyncGroup;
+ @Mock
+ private SurfaceControl mMockTaskSurface;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+ private final InsetsState mInsetsState = new InsetsState();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
+ private Configuration mWindowConfiguration = new Configuration();
private int mCaptionMenuWidthId;
- private int mCaptionMenuShadowRadiusId;
- private int mCaptionMenuCornerRadiusId;
@Before
public void setUp() {
@@ -129,8 +144,6 @@ public class WindowDecorationTests extends ShellTestCase {
mRelayoutParams.mLayoutResId = 0;
mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
- mCaptionMenuShadowRadiusId = R.dimen.test_caption_menu_shadow_radius;
- mCaptionMenuCornerRadiusId = R.dimen.test_caption_menu_corner_radius;
mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
mRelayoutParams.mCornerRadius = CORNER_RADIUS;
@@ -138,6 +151,11 @@ public class WindowDecorationTests extends ShellTestCase {
.create(any(), any(), any());
when(mMockSurfaceControlViewHost.getRootSurfaceControl())
.thenReturn(mMockRootSurfaceControl);
+ when(mMockView.findViewById(anyInt())).thenReturn(mMockView);
+
+ // Add status bar inset so that WindowDecoration does not think task is in immersive mode
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
+ doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
}
@Test
@@ -173,8 +191,7 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -183,7 +200,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -203,12 +220,8 @@ public class WindowDecorationTests extends ShellTestCase {
createMockSurfaceControlBuilder(captionContainerSurface);
mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
- 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)
@@ -217,12 +230,11 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -250,16 +262,15 @@ public class WindowDecorationTests extends ShellTestCase {
}
verify(mMockSurfaceControlFinishT)
- .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+ TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setWindowCrop(taskSurface, 300, 100);
- verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+ .setWindowCrop(mMockTaskSurface, 300, 100);
+ verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+ verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
- .show(taskSurface);
- verify(mMockSurfaceControlStartT)
- .setColor(taskSurface, new float[] {1.f, 1.f, 0.f});
- verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+ .show(mMockTaskSurface);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -296,9 +307,9 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -335,8 +346,7 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
- final TestWindowDecoration windowDecor =
- createWindowDecoration(taskInfo, new SurfaceControl());
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// It shouldn't show the window decoration when it can't obtain the display instance.
@@ -394,8 +404,7 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -414,16 +423,6 @@ public class WindowDecorationTests extends ShellTestCase {
final int height = WindowDecoration.loadDimensionPixelSize(
windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
- final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
- windowDecor.mDecorWindowContext.getResources(),
- mCaptionMenuShadowRadiusId);
- verify(mMockSurfaceControlAddWindowT)
- .setShadowRadius(additionalWindowSurface, shadowRadius);
- final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
- windowDecor.mDecorWindowContext.getResources(),
- mCaptionMenuCornerRadiusId);
- verify(mMockSurfaceControlAddWindowT)
- .setCornerRadius(additionalWindowSurface, cornerRadius);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
@@ -464,8 +463,7 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -505,23 +503,154 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
}
- private TestWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ @Test
+ public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).strictness(
+ LENIENT).startMocking();
+ when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
+
+ mockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testInsetsAddedWhenCaptionIsVisible() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
+ .isVisible());
+ assertTrue(mInsetsState.sourceSize() == 1);
+ assertTrue(mInsetsState.sourceAt(0).getType() == statusBars());
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()), any());
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()), any());
+ }
+
+ @Test
+ public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).strictness(LENIENT).startMocking();
+ when(DesktopModeStatus.isVeiledResizeEnabled()).thenReturn(false);
+
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build();
+ taskInfo.isFocused = true;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
+
+ mockitoSession.finishMocking();
+ }
+
+
+ @Test
+ public void testInsetsRemovedWhenCaptionIsHidden() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder();
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(captionBar()));
+ verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
+ eq(0) /* index */, eq(mandatorySystemGestures()));
+ }
+
+ private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface,
+ taskInfo, mMockTaskSurface, mWindowConfiguration,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
- () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+ () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+ mMockSurfaceControlViewHostFactory);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -556,13 +685,16 @@ public class WindowDecorationTests extends ShellTestCase {
TestWindowDecoration(Context context, DisplayController displayController,
ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
+ Configuration windowConfiguration,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
- surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+ windowConfiguration, surfaceControlBuilderSupplier,
+ surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
+ surfaceControlSupplier, surfaceControlViewHostFactory);
}
@Override
@@ -583,13 +715,11 @@ public class WindowDecorationTests extends ShellTestCase {
int y = mRelayoutParams.mCaptionY;
int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
- int shadowRadius = loadDimensionPixelSize(resources, mCaptionMenuShadowRadiusId);
- int cornerRadius = loadDimensionPixelSize(resources, mCaptionMenuCornerRadiusId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
+ addWindow(R.layout.desktop_mode_window_decor_handle_menu, name,
mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
- width, height, shadowRadius, cornerRadius);
+ width, height);
return additionalWindow;
}
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 4fb80ac13bbf..47a7f3579764 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -257,6 +257,7 @@ cc_benchmark {
"tests/AssetManager2_bench.cpp",
"tests/AttributeResolution_bench.cpp",
"tests/CursorWindow_bench.cpp",
+ "tests/Generic_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp
index 32d2c5b05acb..7eedfdb5c921 100644
--- a/libs/androidfw/ApkParsing.cpp
+++ b/libs/androidfw/ApkParsing.cpp
@@ -56,6 +56,11 @@ const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit,
return nullptr;
}
+ // Make sure file starts with 'lib/' prefix.
+ if (strncmp(fileName, APK_LIB.data(), APK_LIB_LEN) != 0) {
+ return nullptr;
+ }
+
// Make sure there aren't subdirectories by checking if the next / after lib/ is the last slash
if (memchr(fileName + APK_LIB_LEN, '/', fileNameLen - APK_LIB_LEN) != lastSlash) {
return nullptr;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5e04bfea2bef..d056248273b2 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -97,12 +97,17 @@ struct Theme::Entry {
Res_value value;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
- : configuration_(configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+ configurations_.push_back(configuration);
+
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
+AssetManager2::AssetManager2() {
+ configurations_.resize(1);
+}
+
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
BuildDynamicRefTable(apk_assets);
RebuildFilterList();
@@ -266,7 +271,7 @@ void AssetManager2::DumpToLog() const {
auto op = StartOperation();
std::string list;
- for (size_t i = 0; i < apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i < s; ++i) {
const auto& assets = GetApkAssets(i);
base::StringAppendF(&list, "%s,", assets ? assets->GetDebugName().c_str() : "nullptr");
}
@@ -359,7 +364,7 @@ bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
std::string* out) const {
auto op = StartOperation();
uint8_t package_id = 0U;
- for (size_t i = 0; i != apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
const auto& assets = GetApkAssets(i);
if (!assets) {
continue;
@@ -418,7 +423,7 @@ bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
bool AssetManager2::ContainsAllocatedTable() const {
auto op = StartOperation();
- for (size_t i = 0; i != apk_assets_.size(); ++i) {
+ for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
const auto& assets = GetApkAssets(i);
if (assets && assets->IsTableAllocated()) {
return true;
@@ -427,9 +432,16 @@ bool AssetManager2::ContainsAllocatedTable() const {
return false;
}
-void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
- const int diff = configuration_.diff(configuration);
- configuration_ = configuration;
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+ int diff = 0;
+ if (configurations_.size() != configurations.size()) {
+ diff = -1;
+ } else {
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
+ }
+ }
+ configurations_ = std::move(configurations);
if (diff) {
RebuildFilterList();
@@ -626,16 +638,6 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
auto op = StartOperation();
- // Might use this if density_override != 0.
- ResTable_config density_override_config;
-
- // Select our configuration or generate a density override configuration.
- const ResTable_config* desired_config = &configuration_;
- if (density_override != 0 && density_override != configuration_.density) {
- density_override_config = configuration_;
- density_override_config.density = density_override;
- desired_config = &density_override_config;
- }
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
@@ -654,119 +656,160 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
}
const PackageGroup& package_group = package_groups_[package_idx];
- auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
- if (UNLIKELY(!result.has_value())) {
- return base::unexpected(result.error());
- }
+ std::optional<FindEntryResult> final_result;
+ bool final_has_locale = false;
+ bool final_overlaid = false;
+ for (auto & config : configurations_) {
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
+
+ // Select our configuration or generate a density override configuration.
+ const ResTable_config* desired_config = &config;
+ if (density_override != 0 && density_override != config.density) {
+ density_override_config = config;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
+ }
- bool overlaid = false;
- if (!stop_at_first_match && !ignore_configuration) {
- const auto& assets = GetApkAssets(result->cookie);
- if (!assets) {
- ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
- return base::unexpected(std::nullopt);
+ auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+ stop_at_first_match, ignore_configuration);
+ if (UNLIKELY(!result.has_value())) {
+ return base::unexpected(result.error());
}
- if (!assets->IsLoader()) {
- for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
- if (!overlay_entry) {
- // No id map entry exists for this target resource.
- continue;
- }
- if (overlay_entry.IsInlineValue()) {
- // The target resource is overlaid by an inline value not represented by a resource.
- ConfigDescription best_frro_config;
- Res_value best_frro_value;
- bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
- if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
- && config.match(*desired_config)) {
- frro_found = true;
- best_frro_config = config;
- best_frro_value = value;
+ bool overlaid = false;
+ if (!stop_at_first_match && !ignore_configuration) {
+ const auto& assets = GetApkAssets(result->cookie);
+ if (!assets) {
+ ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+ return base::unexpected(std::nullopt);
+ }
+ if (!assets->IsLoader()) {
+ for (const auto& id_map : package_group.overlays_) {
+ auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ if (!overlay_entry) {
+ // No id map entry exists for this target resource.
+ continue;
+ }
+ if (overlay_entry.IsInlineValue()) {
+ // The target resource is overlaid by an inline value not represented by a resource.
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
}
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
+ result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+ result->cookie = id_map.cookie;
+
+ if (UNLIKELY(logging_enabled)) {
+ last_resolution_.steps.push_back(Resolution::Step{
+ Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+ if (auto path = assets->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
+ }
+ continue;
}
- if (!frro_found) {
+
+ auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(overlay_result))) {
+ return base::unexpected(overlay_result.error());
+ }
+ if (!overlay_result.has_value()) {
+ continue;
+ }
+
+ if (!overlay_result->config.isBetterThan(result->config, desired_config)
+ && overlay_result->config.compare(result->config) != 0) {
+ // The configuration of the entry for the overlay must be equal to or better than the
+ // target configuration to be chosen as the better value.
continue;
}
- result->entry = best_frro_value;
+
+ result->cookie = overlay_result->cookie;
+ result->entry = overlay_result->entry;
+ result->config = overlay_result->config;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
- result->cookie = id_map.cookie;
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie});
- if (auto path = assets->GetPath()) {
- const std::string overlay_path = path->data();
- if (IsFabricatedOverlay(overlay_path)) {
- // FRRO don't have package name so we use the creating package here.
- String8 frro_name = String8("FRRO");
- // Get the first part of it since the expected one should be like
- // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
- // under /data/resource-cache/.
- const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
- const size_t end = name.find('-');
- if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
- frro_name.append(base::StringPrintf(" created by %s",
- name.substr(0 /* pos */,
- end).c_str()).c_str());
- }
- last_resolution_.best_package_name = frro_name;
- } else {
- last_resolution_.best_package_name = result->package_name->c_str();
- }
- }
+ Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+ overlay_result->config.toString()});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
overlaid = true;
}
- continue;
- }
-
- auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
- false /* stop_at_first_match */,
- false /* ignore_configuration */);
- if (UNLIKELY(IsIOError(overlay_result))) {
- return base::unexpected(overlay_result.error());
- }
- if (!overlay_result.has_value()) {
- continue;
}
+ }
+ }
- if (!overlay_result->config.isBetterThan(result->config, desired_config)
- && overlay_result->config.compare(result->config) != 0) {
- // The configuration of the entry for the overlay must be equal to or better than the target
- // configuration to be chosen as the better value.
- continue;
- }
-
- result->cookie = overlay_result->cookie;
- result->entry = overlay_result->entry;
- result->config = overlay_result->config;
- result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
- if (UNLIKELY(logging_enabled)) {
- last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
- overlay_result->cookie});
- last_resolution_.best_package_name =
- overlay_result->package_name->c_str();
- overlaid = true;
- }
+ bool has_locale = false;
+ if (result->config.locale == 0) {
+ if (default_locale_ != 0) {
+ ResTable_config conf;
+ conf.locale = default_locale_;
+ // Since we know conf has a locale and only a locale, match will tell us if that locale
+ // matches
+ has_locale = conf.match(config);
}
+ } else {
+ has_locale = true;
+ }
+
+ // if we don't have a result yet
+ if (!final_result ||
+ // or this config is better before the locale than the existing result
+ result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+ // or the existing config isn't better before locale and this one specifies a locale
+ // whereas the existing one doesn't
+ (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+ && has_locale && !final_has_locale)) {
+ final_result = result.value();
+ final_overlaid = overlaid;
+ final_has_locale = has_locale;
}
}
if (UNLIKELY(logging_enabled)) {
- last_resolution_.cookie = result->cookie;
- last_resolution_.type_string_ref = result->type_string_ref;
- last_resolution_.entry_string_ref = result->entry_string_ref;
- last_resolution_.best_config_name = result->config.toString();
- if (!overlaid) {
- last_resolution_.best_package_name = result->package_name->c_str();
+ last_resolution_.cookie = final_result->cookie;
+ last_resolution_.type_string_ref = final_result->type_string_ref;
+ last_resolution_.entry_string_ref = final_result->entry_string_ref;
+ last_resolution_.best_config_name = final_result->config.toString();
+ if (!final_overlaid) {
+ last_resolution_.best_package_name = final_result->package_name->c_str();
}
}
- return result;
+ return *final_result;
}
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -784,8 +827,10 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
- const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
-
+ const bool use_filtered = !ignore_configuration && std::find_if(
+ configurations_.begin(), configurations_.end(),
+ [&desired_config](auto& value) { return &desired_config == &value; })
+ != configurations_.end();
const size_t package_count = package_group.packages_.size();
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -818,9 +863,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
// We can skip calling ResTable_config::match() if the caller does not care for the
// configuration to match or if we're using the list of types that have already had their
- // configuration matched.
+ // configuration matched. The exception to this is when the user has multiple locales set
+ // because the filtered list will then have values from multiple locales and we will need to
+ // call match() to make sure the current entry matches the config we are currently checking.
const ResTable_config& this_config = type_entry->config;
- if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+ if (!((use_filtered && (configurations_.size() == 1))
+ || ignore_configuration || this_config.match(desired_config))) {
continue;
}
@@ -834,8 +882,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
} else {
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
continue;
}
@@ -851,8 +898,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
if (!offset.has_value()) {
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
continue;
}
@@ -865,8 +911,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(Resolution::Step{resolution_type,
- this_config.toString(),
- cookie});
+ cookie, this_config.toString()});
}
// Any configuration will suffice, so break.
@@ -895,11 +940,11 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
+ .dynamic_ref_table = package_group.dynamic_ref_table.get(),
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
(*best_entry_verified)->key()),
- .dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
@@ -943,27 +988,40 @@ std::string AssetManager2::GetLastResourceResolution() const {
}
std::stringstream log_stream;
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
- "\tFor config - %s", resid, resource_name_string.c_str(),
- configuration_.toString().c_str());
-
+ if (configurations_.size() == 1) {
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+ "\tFor config - %s", resid, resource_name_string.c_str(),
+ configurations_[0].toString().c_str());
+ } else {
+ ResTable_config conf = configurations_[0];
+ conf.clearLocale();
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
+ resid, resource_name_string.c_str(), conf.toString().c_str());
+ char str[40];
+ str[0] = '\0';
+ for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+ iter->getBcp47Locale(str);
+ log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
+ }
+ }
for (const Resolution::Step& step : last_resolution_.steps) {
- const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = {
- {Resolution::Step::Type::INITIAL, "Found initial"},
- {Resolution::Step::Type::BETTER_MATCH, "Found better"},
- {Resolution::Step::Type::OVERLAID, "Overlaid"},
- {Resolution::Step::Type::OVERLAID_INLINE, "Overlaid inline"},
- {Resolution::Step::Type::SKIPPED, "Skipped"},
- {Resolution::Step::Type::NO_ENTRY, "No entry"}
+ constexpr static std::array kStepStrings = {
+ "Found initial",
+ "Found better",
+ "Overlaid",
+ "Overlaid inline",
+ "Skipped",
+ "No entry"
};
- const auto prefix = kStepStrings.find(step.type);
- if (prefix == kStepStrings.end()) {
+ if (step.type < Resolution::Step::Type::INITIAL
+ || step.type > Resolution::Step::Type::NO_ENTRY) {
continue;
}
+ const auto prefix = kStepStrings[int(step.type) - int(Resolution::Step::Type::INITIAL)];
const auto& assets = GetApkAssets(step.cookie);
- log_stream << "\n\t" << prefix->second << ": "
- << (assets ? assets->GetDebugName() : "<null>") << " #" << step.cookie;
+ log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
+ << " #" << step.cookie;
if (!step.config_name.empty()) {
log_stream << " - " << step.config_name;
}
@@ -1100,16 +1158,19 @@ base::expected<std::monostate, NullOrIOError> AssetManager2::ResolveReference(
}
}
-const std::vector<uint32_t> AssetManager2::GetBagResIdStack(uint32_t resid) const {
- auto cached_iter = cached_bag_resid_stacks_.find(resid);
- if (cached_iter != cached_bag_resid_stacks_.end()) {
- return cached_iter->second;
+base::expected<const std::vector<uint32_t>*, NullOrIOError> AssetManager2::GetBagResIdStack(
+ uint32_t resid) const {
+ auto it = cached_bag_resid_stacks_.find(resid);
+ if (it != cached_bag_resid_stacks_.end()) {
+ return &it->second;
+ }
+ std::vector<uint32_t> stacks;
+ if (auto maybe_bag = GetBag(resid, stacks); UNLIKELY(IsIOError(maybe_bag))) {
+ return base::unexpected(maybe_bag.error());
}
- std::vector<uint32_t> found_resids;
- GetBag(resid, found_resids);
- cached_bag_resid_stacks_.emplace(resid, found_resids);
- return found_resids;
+ it = cached_bag_resid_stacks_.emplace(resid, std::move(stacks)).first;
+ return &it->second;
}
base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
@@ -1126,9 +1187,15 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
}
base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const {
- std::vector<uint32_t> found_resids;
- const auto bag = GetBag(resid, found_resids);
- cached_bag_resid_stacks_.emplace(resid, std::move(found_resids));
+ auto resid_stacks_it = cached_bag_resid_stacks_.find(resid);
+ if (resid_stacks_it == cached_bag_resid_stacks_.end()) {
+ resid_stacks_it = cached_bag_resid_stacks_.emplace(resid, std::vector<uint32_t>{}).first;
+ }
+ const auto bag = GetBag(resid, resid_stacks_it->second);
+ if (UNLIKELY(IsIOError(bag))) {
+ cached_bag_resid_stacks_.erase(resid_stacks_it);
+ return base::unexpected(bag.error());
+ }
return bag;
}
@@ -1426,11 +1493,14 @@ void AssetManager2::RebuildFilterList() {
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- if (type_entry.config.match(configuration_)) {
- if (!group) {
- group = &package.filtered_configs_.editItemAt(type_id - 1);
+ for (auto & config : configurations_) {
+ if (type_entry.config.match(config)) {
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
+ break;
}
- group->type_entries.push_back(&type_entry);
}
}
});
@@ -1441,25 +1511,40 @@ void AssetManager2::RebuildFilterList() {
}
void AssetManager2::InvalidateCaches(uint32_t diff) {
- cached_bag_resid_stacks_.clear();
+ cached_resolved_values_.clear();
if (diff == 0xffffffffu) {
// Everything must go.
cached_bags_.clear();
+ cached_bag_resid_stacks_.clear();
return;
}
// Be more conservative with what gets purged. Only if the bag has other possible
// variations with respect to what changed (diff) should we remove it.
- for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
- if (diff & iter->second->type_spec_flags) {
- iter = cached_bags_.erase(iter);
+ for (auto stack_it = cached_bag_resid_stacks_.begin();
+ stack_it != cached_bag_resid_stacks_.end();) {
+ const auto it = cached_bags_.find(stack_it->first);
+ if (it == cached_bags_.end()) {
+ stack_it = cached_bag_resid_stacks_.erase(stack_it);
+ } else if ((diff & it->second->type_spec_flags) != 0) {
+ cached_bags_.erase(it);
+ stack_it = cached_bag_resid_stacks_.erase(stack_it);
} else {
- ++iter;
+ ++stack_it; // Keep the item in both caches.
}
}
- cached_resolved_values_.clear();
+ // Need to ensure that both bag caches are consistent, as we populate them in the same function.
+ // Iterate over the cached bags to erase the items without the corresponding resid_stack cache
+ // items.
+ for (auto it = cached_bags_.begin(); it != cached_bags_.end();) {
+ if ((diff & it->second->type_spec_flags) != 0) {
+ it = cached_bags_.erase(it);
+ } else {
+ ++it;
+ }
+ }
}
uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const {
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 89835742c8ff..5f98b8f8db43 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -294,14 +294,14 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
dtohl(header->version), kIdmapCurrentVersion);
return {};
}
+ std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
+ if (!target_path) {
+ return {};
+ }
std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path");
if (!overlay_path) {
return {};
}
- std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
- if (!target_path) {
- return {};
- }
if (!ReadString(&data_ptr, &data_size, "target name") ||
!ReadString(&data_ptr, &data_size, "debug info")) {
return {};
@@ -364,7 +364,7 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
return std::unique_ptr<LoadedIdmap>(
new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
target_inline_entries, target_inline_entry_values, configurations,
- overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
+ overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index fbfae5e2bcbe..c9d5e074271b 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -494,6 +494,8 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
&loaded_package->package_name_);
+ const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
@@ -502,6 +504,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
+ if (only_overlayable && child_chunk.type() != RES_TABLE_OVERLAYABLE_TYPE) {
+ continue;
+ }
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE: {
const auto pool_address = child_chunk.header<ResChunk_header>();
@@ -655,6 +660,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
<< name_to_actor_it->first << "'.";
return {};
}
+ if (only_overlayable) {
+ break;
+ }
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -800,14 +808,21 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
}
+ const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
const size_t package_count = dtohl(header->packageCount);
size_t packages_seen = 0;
- packages_.reserve(package_count);
+ if (!only_overlayable) {
+ packages_.reserve(package_count);
+ }
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
+ if (only_overlayable && child_chunk.type() != RES_TABLE_PACKAGE_TYPE) {
+ continue;
+ }
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE:
// Only use the first string pool. Ignore others.
@@ -837,6 +852,10 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
return false;
}
packages_.push_back(std::move(loaded_package));
+ if (only_overlayable) {
+ // Overlayable is always in the first package, no need to process anything else.
+ return true;
+ }
} break;
default:
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index c13827fe29c3..4c992becda7c 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1769,13 +1769,21 @@ ResXMLTree::~ResXMLTree()
status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
{
+ const ResChunk_header* chunk = nullptr;
+ const ResChunk_header* lastChunk = nullptr;
+
uninit();
mEventCode = START_DOCUMENT;
if (!data || !size) {
return (mError=BAD_TYPE);
}
-
+ if (size < sizeof(ResXMLTree_header)) {
+ ALOGW("Bad XML block: total size %d is less than the header size %d\n",
+ int(size), int(sizeof(ResXMLTree_header)));
+ mError = BAD_TYPE;
+ goto done;
+ }
if (copyData) {
mOwnedData = malloc(size);
if (mOwnedData == NULL) {
@@ -1792,9 +1800,15 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
(int)dtohs(mHeader->header.headerSize),
(int)dtohl(mHeader->header.size), (int)size);
mError = BAD_TYPE;
- restart();
- return mError;
+ goto done;
}
+ if (dtohs(mHeader->header.type) != RES_XML_TYPE) {
+ ALOGW("Bad XML block: expected root block type %d, got %d\n",
+ int(RES_XML_TYPE), int(dtohs(mHeader->header.type)));
+ mError = BAD_TYPE;
+ goto done;
+ }
+
mDataEnd = ((const uint8_t*)mHeader) + mSize;
mStrings.uninit();
@@ -1804,9 +1818,8 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
// First look for a couple interesting chunks: the string block
// and first XML node.
- const ResChunk_header* chunk =
- (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
- const ResChunk_header* lastChunk = chunk;
+ chunk = (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
+ lastChunk = chunk;
while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
@@ -1860,7 +1873,11 @@ status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
mError = mStrings.getError();
done:
- restart();
+ if (mError) {
+ uninit();
+ } else {
+ restart();
+ }
return mError;
}
@@ -2551,6 +2568,22 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
return false;
}
+bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+ }
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
@@ -5436,37 +5469,66 @@ bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue)
return U16StringToInt(s, len, outValue);
}
-bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
-{
- while (len > 0 && isspace16(*s)) {
- s++;
- len--;
+template <typename T>
+bool parseFloatingPoint(const char16_t* inBuf, size_t inLen, char* tempBuf,
+ const char** outEnd, T& out){
+ while (inLen > 0 && isspace16(*inBuf)) {
+ inBuf++;
+ inLen--;
}
- if (len <= 0) {
+ if (inLen <= 0) {
return false;
}
- char buf[128];
int i=0;
- while (len > 0 && *s != 0 && i < 126) {
- if (*s > 255) {
+ while (inLen > 0 && *inBuf != 0 && i < 126) {
+ if (*inBuf > 255) {
return false;
}
- buf[i++] = *s++;
- len--;
+ tempBuf[i++] = *inBuf++;
+ inLen--;
}
- if (len > 0) {
+ if (inLen > 0) {
+ return false;
+ }
+ if ((tempBuf[0] < '0' || tempBuf[0] > '9') && tempBuf[0] != '.' && tempBuf[0] != '-' && tempBuf[0] != '+') {
return false;
}
- if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
+
+ tempBuf[i] = 0;
+ if constexpr(std::is_same_v<T, float>) {
+ out = strtof(tempBuf, (char**)outEnd);
+ } else {
+ out = strtod(tempBuf, (char**)outEnd);
+ }
+ return true;
+}
+
+bool ResTable::stringToDouble(const char16_t* s, size_t len, double& d){
+ char buf[128];
+ const char* end = nullptr;
+ if (!parseFloatingPoint(s, len, buf, &end, d)) {
return false;
}
- buf[i] = 0;
- const char* end;
- float f = strtof(buf, (char**)&end);
+ while (*end != 0 && isspace((unsigned char)*end)) {
+ end++;
+ }
+
+ return *end == 0;
+}
+
+bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
+{
+ char buf[128];
+ const char* end = nullptr;
+ float f;
+
+ if (!parseFloatingPoint(s, len, buf, &end, f)) {
+ return false;
+ }
if (*end != 0 && !isspace((unsigned char)*end)) {
// Might be a unit...
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 52e7a70521a1..d7b5914130ee 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -40,17 +40,24 @@ class _ZipEntryRO {
public:
ZipEntry entry;
std::string_view name;
- void *cookie;
+ void *cookie = nullptr;
- _ZipEntryRO() : cookie(NULL) {}
+ _ZipEntryRO() = default;
~_ZipEntryRO() {
- EndIteration(cookie);
+ EndIteration(cookie);
+ }
+
+ android::ZipEntryRO convertToPtr() {
+ _ZipEntryRO* result = new _ZipEntryRO;
+ result->entry = std::move(this->entry);
+ result->name = std::move(this->name);
+ result->cookie = std::exchange(this->cookie, nullptr);
+ return result;
}
private:
- _ZipEntryRO(const _ZipEntryRO& other);
- _ZipEntryRO& operator=(const _ZipEntryRO& other);
+ DISALLOW_COPY_AND_ASSIGN(_ZipEntryRO);
};
ZipFileRO::~ZipFileRO() {
@@ -94,17 +101,15 @@ ZipFileRO::~ZipFileRO() {
ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
{
- _ZipEntryRO* data = new _ZipEntryRO;
-
- data->name = entryName;
+ _ZipEntryRO data;
+ data.name = entryName;
- const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
+ const int32_t error = FindEntry(mHandle, entryName, &(data.entry));
if (error) {
- delete data;
- return NULL;
+ return nullptr;
}
- return (ZipEntryRO) data;
+ return data.convertToPtr();
}
/*
@@ -143,35 +148,50 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
}
bool ZipFileRO::startIteration(void** cookie) {
- return startIteration(cookie, NULL, NULL);
+ return startIteration(cookie, nullptr, nullptr);
}
-bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix)
-{
- _ZipEntryRO* ze = new _ZipEntryRO;
- int32_t error = StartIteration(mHandle, &(ze->cookie),
+bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) {
+ auto result = startIterationOrError(prefix, suffix);
+ if (!result.ok()) {
+ return false;
+ }
+ *cookie = result.value();
+ return true;
+}
+
+base::expected<void*, int32_t>
+ZipFileRO::startIterationOrError(const char* prefix, const char* suffix) {
+ _ZipEntryRO ze;
+ int32_t error = StartIteration(mHandle, &(ze.cookie),
prefix ? prefix : "", suffix ? suffix : "");
if (error) {
ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
- delete ze;
- return false;
+ return base::unexpected(error);
}
- *cookie = ze;
- return true;
+ return ze.convertToPtr();
}
-ZipEntryRO ZipFileRO::nextEntry(void* cookie)
-{
+ZipEntryRO ZipFileRO::nextEntry(void* cookie) {
+ auto result = nextEntryOrError(cookie);
+ if (!result.ok()) {
+ return nullptr;
+ }
+ return result.value();
+}
+
+base::expected<ZipEntryRO, int32_t> ZipFileRO::nextEntryOrError(void* cookie) {
_ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie);
int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
if (error) {
if (error != -1) {
ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
ErrorCodeString(error));
+ return base::unexpected(error);
}
- return NULL;
+ return nullptr;
}
return &(ze->entry);
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index fc391bc2ce67..d9ff35b49e0a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@ class AssetManager2 {
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
- AssetManager2() = default;
+ AssetManager2();
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
@@ -156,10 +156,14 @@ class AssetManager2 {
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfiguration(const ResTable_config& configuration);
+ void SetConfigurations(std::vector<ResTable_config> configurations);
- inline const ResTable_config& GetConfiguration() const {
- return configuration_;
+ inline const std::vector<ResTable_config>& GetConfigurations() const {
+ return configurations_;
+ }
+
+ inline void SetDefaultLocale(uint32_t default_locale) {
+ default_locale_ = default_locale;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -243,9 +247,14 @@ class AssetManager2 {
friend AssetManager2;
friend Theme;
SelectedValue() = default;
- SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry) :
- cookie(entry.cookie), data(entry.value.data), type(entry.value.dataType),
- flags(bag->type_spec_flags), resid(0U), config({}) {};
+ SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry)
+ : cookie(entry.cookie),
+ data(entry.value.data),
+ type(entry.value.dataType),
+ flags(bag->type_spec_flags),
+ resid(0U),
+ config() {
+ }
// The cookie representing the ApkAssets in which the value resides.
ApkAssetsCookie cookie = kInvalidCookie;
@@ -327,7 +336,8 @@ class AssetManager2 {
// resource data failed.
base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const;
- const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const;
+ base::expected<const std::vector<uint32_t>*, NullOrIOError> GetBagResIdStack(
+ uint32_t resid) const;
// Resets the resource resolution structures in preparation for the next resource retrieval.
void ResetResourceResolution() const;
@@ -459,9 +469,11 @@ class AssetManager2 {
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
- // The current configuration set for this AssetManager. When this changes, cached resources
+ uint32_t default_locale_;
+
+ // The current configurations set for this AssetManager. When this changes, cached resources
// may need to be purged.
- ResTable_config configuration_ = {};
+ std::vector<ResTable_config> configurations_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
@@ -495,10 +507,10 @@ class AssetManager2 {
// Marks what kind of override this step was.
Type type;
+ ApkAssetsCookie cookie = kInvalidCookie;
+
// Built name of configuration for this step.
String8 config_name;
-
- ApkAssetsCookie cookie = kInvalidCookie;
};
// Last resolved resource ID.
diff --git a/libs/androidfw/include/androidfw/Errors.h b/libs/androidfw/include/androidfw/Errors.h
index 948162d10480..6667747fc581 100644
--- a/libs/androidfw/include/androidfw/Errors.h
+++ b/libs/androidfw/include/androidfw/Errors.h
@@ -34,7 +34,7 @@ using NullOrIOError = std::variant<std::nullopt_t, IOError>;
// Checks whether the result holds an unexpected I/O error.
template <typename T>
-static inline bool IsIOError(const base::expected<T, NullOrIOError> result) {
+static inline bool IsIOError(const base::expected<T, NullOrIOError>& result) {
return !result.has_value() && std::holds_alternative<IOError>(result.error());
}
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 4d5844eaa069..865a298f8389 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -86,6 +86,17 @@ struct IDiagnostics {
DiagMessageActual actual = message.Build();
Log(Level::Note, actual);
}
+
+ virtual void SetVerbose(bool val) {
+ verbose_ = val;
+ }
+
+ virtual bool IsVerbose() {
+ return verbose_;
+ }
+
+ private:
+ bool verbose_ = false;
};
class SourcePathDiagnostics : public IDiagnostics {
@@ -105,6 +116,14 @@ class SourcePathDiagnostics : public IDiagnostics {
return error;
}
+ void SetVerbose(bool val) override {
+ diag_->SetVerbose(val);
+ }
+
+ bool IsVerbose() override {
+ return diag_->IsVerbose();
+ }
+
private:
Source source_;
IDiagnostics* diag_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 4d12885ad291..3a7287187781 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -96,6 +96,9 @@ enum : package_property_t {
// The apk assets is owned by the application running in this process and incremental crash
// protections for this APK must be disabled.
PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1U << 4U,
+
+ // The apk assets only contain the overlayable declarations information.
+ PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
};
struct OverlayableInfo {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 631bda4f886c..fdb355192676 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,6 +1375,8 @@ struct ResTable_config
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+ bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+
String8 toString() const;
};
@@ -1870,6 +1872,8 @@ struct FabricatedOverlayEntryParameters {
DataValue data_value;
std::string data_string_value;
std::optional<android::base::borrowed_fd> data_binary_value;
+ off64_t binary_data_offset;
+ size_t binary_data_size;
std::string configuration;
};
@@ -2162,6 +2166,7 @@ public:
static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
+ static bool stringToDouble(const char16_t* s, size_t len, double& outValue);
// Used with stringToValue.
class Accessor
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 10f6d0655bf4..be1f98f4843d 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -37,6 +37,8 @@
#include <unistd.h>
#include <time.h>
+#include <android-base/expected.h>
+
#include <util/map_ptr.h>
#include <utils/Compat.h>
@@ -102,6 +104,11 @@ public:
*/
bool startIteration(void** cookie);
bool startIteration(void** cookie, const char* prefix, const char* suffix);
+ /*
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<void*, int32_t> startIterationOrError(const char* prefix, const char* suffix);
/**
* Return the next entry in iteration order, or NULL if there are no more
@@ -109,6 +116,12 @@ public:
*/
ZipEntryRO nextEntry(void* cookie);
+ /**
+ * Same as above, but returns the error code in case of failure.
+ * #see libziparchive/zip_error.h.
+ */
+ base::expected<ZipEntryRO, int32_t> nextEntryOrError(void* cookie);
+
void endIteration(void* cookie);
void releaseEntry(ZipEntryRO entry) const;
diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp
index 62e88c619e5c..ac1dc9b88463 100644
--- a/libs/androidfw/tests/ApkParsing_test.cpp
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -74,4 +74,10 @@ TEST(ApkParsingTest, InvalidFileAtRoot) {
auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
ASSERT_THAT(lastSlash, IsNull());
}
+
+TEST(ApkParsingTest, InvalidPrefix) {
+ const char* path = "assets/libhello.so";
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ ASSERT_THAT(lastSlash, IsNull());
+}
} \ No newline at end of file
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 6fae72a6d10e..2caa98c35971 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,10 +228,12 @@ static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
ResTable_config config;
memset(&config, 0, sizeof(config));
+ std::vector<ResTable_config> configs;
+ configs.push_back(config);
while (state.KeepRunning()) {
- config.sdkVersion = ~config.sdkVersion;
- assets.SetConfiguration(config);
+ configs[0].sdkVersion = ~configs[0].sdkVersion;
+ assets.SetConfigurations(configs);
}
}
BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index df3fa02ce44c..c62f095e9dac 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@ TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@ TEST_F(AssetManager2Test, FindsResourceFromMultipleApkAssets) {
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@ TEST_F(AssetManager2Test, ResolveDeepIdReference) {
TEST_F(AssetManager2Test, DensityOverride) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
- assetmanager.SetConfiguration({
+ assetmanager.SetConfigurations({{
.density = ResTable_config::DENSITY_XHIGH,
.sdkVersion = 21,
- });
+ }});
auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutEnablingReturnsEmpty) {
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
assetmanager.SetResourceResolutionLoggingEnabled(false);
@@ -736,7 +736,7 @@ TEST_F(AssetManager2Test, GetLastPathWithoutResolutionReturnsEmpty) {
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@ TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) {
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@ TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@ TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@ TEST_F(AssetManager2Test, GetOverlayablesToString) {
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({overlayable_assets_});
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index b97dd96f8934..8b883f4ed1df 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_
AssetManager2 assetmanager;
assetmanager.SetApkAssets(apk_assets);
if (config != nullptr) {
- assetmanager.SetConfiguration(*config);
+ assetmanager.SetConfigurations({*config});
}
while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Generic_bench.cpp b/libs/androidfw/tests/Generic_bench.cpp
new file mode 100644
index 000000000000..4c978e889f83
--- /dev/null
+++ b/libs/androidfw/tests/Generic_bench.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <map>
+#include <unordered_map>
+
+#include "benchmark/benchmark.h"
+
+namespace android {
+
+template <class Map = std::unordered_map<uint32_t, std::vector<uint32_t>>>
+static Map prepare_map() {
+ Map map;
+ std::vector<uint32_t> vec;
+ for (int i = 0; i < 1000; ++i) {
+ map.emplace(i, vec);
+ }
+ return map;
+}
+
+static void BM_hashmap_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<>();
+ auto val = map.size() - 1;
+ std::vector<uint32_t> vec;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.emplace(val, vec));
+ }
+}
+BENCHMARK(BM_hashmap_emplace_same);
+static void BM_hashmap_try_emplace_same(benchmark::State& state) {
+ auto map = prepare_map();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.try_emplace(val));
+ }
+}
+BENCHMARK(BM_hashmap_try_emplace_same);
+static void BM_hashmap_find(benchmark::State& state) {
+ auto map = prepare_map<>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.find(val));
+ }
+}
+BENCHMARK(BM_hashmap_find);
+
+static void BM_hashmap_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.emplace(i++, vec);
+ }
+}
+BENCHMARK(BM_hashmap_emplace_diff);
+static void BM_hashmap_try_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.try_emplace(i++);
+ }
+}
+BENCHMARK(BM_hashmap_try_emplace_diff);
+static void BM_hashmap_find_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ if (map.find(i++) == map.end()) {
+ map.emplace(i - 1, vec);
+ }
+ }
+}
+BENCHMARK(BM_hashmap_find_emplace_diff);
+
+static void BM_treemap_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ std::vector<uint32_t> vec;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.emplace(val, vec));
+ }
+}
+BENCHMARK(BM_treemap_emplace_same);
+static void BM_treemap_try_emplace_same(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.try_emplace(val));
+ }
+}
+BENCHMARK(BM_treemap_try_emplace_same);
+static void BM_treemap_find(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ auto val = map.size() - 1;
+ for (auto&& _ : state) {
+ benchmark::DoNotOptimize(map.find(val));
+ }
+}
+BENCHMARK(BM_treemap_find);
+
+static void BM_treemap_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.emplace(i++, vec);
+ }
+}
+BENCHMARK(BM_treemap_emplace_diff);
+static void BM_treemap_try_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ auto i = map.size();
+ for (auto&& _ : state) {
+ map.try_emplace(i++);
+ }
+}
+BENCHMARK(BM_treemap_try_emplace_diff);
+static void BM_treemap_find_emplace_diff(benchmark::State& state) {
+ auto map = prepare_map();
+ std::vector<uint32_t> vec;
+ auto i = map.size();
+ for (auto&& _ : state) {
+ if (map.find(i++) == map.end()) {
+ map.emplace(i - 1, vec);
+ }
+ }
+}
+BENCHMARK(BM_treemap_find_emplace_diff);
+
+} // namespace android \ No newline at end of file
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index e08a6a7f277d..181d1411fb91 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@ TEST_F(ThemeTest, ThemeRebase) {
ResTable_config night{};
night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
night.version = 8u;
- am_night.SetConfiguration(night);
+ am_night.SetConfigurations({night});
auto theme = am.NewTheme();
{
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index c87c15669a09..ff1eedb8eacb 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -28,6 +28,19 @@ license {
],
}
+aconfig_declarations {
+ name: "hwui_flags",
+ package: "com.android.graphics.hwui.flags",
+ srcs: [
+ "aconfig/hwui_flags.aconfig",
+ ],
+}
+
+cc_aconfig_library {
+ name: "hwui_flags_cc_lib",
+ aconfig_declarations: "hwui_flags",
+}
+
cc_defaults {
name: "hwui_defaults",
defaults: [
@@ -44,7 +57,7 @@ cc_defaults {
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
"-DATRACE_TAG=ATRACE_TAG_VIEW",
- "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-DLOG_TAG=\"HWUI\"",
"-Wall",
"-Wthread-safety",
"-Wno-unused-parameter",
@@ -130,6 +143,7 @@ cc_defaults {
"libcrypto",
"libsync",
"libui",
+ "aconfig_text_flags_c_lib",
],
static_libs: [
"libEGL_blobCache",
@@ -139,6 +153,7 @@ cc_defaults {
"libstatspull_lazy",
"libstatssocket_lazy",
"libtonemap",
+ "hwui_flags_cc_lib",
],
},
host: {
@@ -514,6 +529,7 @@ cc_defaults {
"canvas/CanvasOpRasterizer.cpp",
"effects/StretchEffect.cpp",
"effects/GainmapRenderer.cpp",
+ "pipeline/skia/BackdropFilterDrawable.cpp",
"pipeline/skia/HolePunch.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
@@ -697,11 +713,13 @@ cc_test {
],
static_libs: [
+ "libflagtest",
"libgmock",
"libhwui_static",
],
shared_libs: [
"libmemunreachable",
+ "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
@@ -741,6 +759,7 @@ cc_test {
"tests/unit/TestUtilsTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
+ "tests/unit/UnderlineTest.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index b656b6ac8204..4d020c567972 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -16,6 +16,11 @@
#include "AutoBackendTextureRelease.h"
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/MutableTextureState.h>
#include "renderthread/RenderThread.h"
#include "utils/Color.h"
#include "utils/PaintUtils.h"
@@ -30,15 +35,47 @@ AutoBackendTextureRelease::AutoBackendTextureRelease(GrDirectContext* context,
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
- GrBackendFormat backendFormat =
- GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+ GrBackendFormat backendFormat;
+ GrBackendApi backend = context->backend();
+ if (backend == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else if (backend == GrBackendApi::kVulkan) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+ buffer,
+ desc.format,
+ false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+ }
LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
__FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
", AHardwareBuffer_Format==%" PRIu32 ".",
static_cast<int>(context->backend()), desc.format);
- mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
- context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
- createProtectedImage, backendFormat, false);
LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
__FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
", protected==%d",
@@ -70,7 +107,7 @@ void AutoBackendTextureRelease::unref(bool releaseImage) {
// releaseProc is invoked by SkImage, when texture is no longer in use.
// "releaseContext" contains an "AutoBackendTextureRelease*".
-static void releaseProc(SkImage::ReleaseContext releaseContext) {
+static void releaseProc(SkImages::ReleaseContext releaseContext) {
AutoBackendTextureRelease* textureRelease =
reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
textureRelease->unref(false);
@@ -83,10 +120,10 @@ void AutoBackendTextureRelease::makeImage(AHardwareBuffer* buffer,
AHardwareBuffer_describe(buffer, &desc);
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
// The following ref will be counteracted by Skia calling releaseProc, either during
- // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
- // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+ // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must
+ // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure.
ref();
- mImage = SkImage::MakeFromTexture(
+ mImage = SkImages::BorrowTextureFrom(
context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
}
@@ -105,8 +142,8 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context)
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
- GrBackendSurfaceMutableState newState(VK_IMAGE_LAYOUT_UNDEFINED,
- VK_QUEUE_FAMILY_FOREIGN_EXT);
+ skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_QUEUE_FAMILY_FOREIGN_EXT);
// The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
// releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
new file mode 100644
index 000000000000..1a5b938d6eed
--- /dev/null
+++ b/libs/hwui/ColorFilter.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COLORFILTER_H_
+#define COLORFILTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "GraphicsJNI.h"
+#include "SkColorFilter.h"
+#include "SkiaWrapper.h"
+
+namespace android {
+namespace uirenderer {
+
+class ColorFilter : public SkiaWrapper<SkColorFilter> {
+public:
+ static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+
+protected:
+ ColorFilter() = default;
+};
+
+class BlendModeColorFilter : public ColorFilter {
+public:
+ BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {}
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); }
+
+private:
+ const SkColor mColor;
+ const SkBlendMode mMode;
+};
+
+class LightingFilter : public ColorFilter {
+public:
+ LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {}
+
+ void setMul(SkColor mul) {
+ mMul = mul;
+ discardInstance();
+ }
+
+ void setAdd(SkColor add) {
+ mAdd = add;
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); }
+
+private:
+ SkColor mMul;
+ SkColor mAdd;
+};
+
+class ColorMatrixColorFilter : public ColorFilter {
+public:
+ ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {}
+
+ void setMatrix(std::vector<float>&& matrix) {
+ mMatrix = std::move(matrix);
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override {
+ return SkColorFilters::Matrix(mMatrix.data());
+ }
+
+private:
+ std::vector<float> mMatrix;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // COLORFILTER_H_
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index eb5878d95561..8c180da9c84f 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -54,6 +54,8 @@ public:
mImpl->updateChildren(std::move(updateFn));
}
+ void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); }
+
[[nodiscard]] explicit operator bool() const {
return mImpl.get() != nullptr;
}
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c633b9..d21f07efe36a 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 000000000000..00d049cde925
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+ return com_android_text_flags_fix_double_underline();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+ return com_android_text_flags_deprecate_ui_fonts();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+} // namespace text_feature
+
+} // namespace android
+
+#endif // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8191f5e6a83a..a958a091a830 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -15,6 +15,8 @@
*/
#include "FrameInfo.h"
+#include <gui/TraceUtils.h>
+
#include <cstring>
namespace android {
@@ -51,6 +53,30 @@ static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 23,
void FrameInfo::importUiThreadInfo(int64_t* info) {
memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+ mSkippedFrameReason.reset();
+}
+
+const char* toString(SkippedFrameReason reason) {
+ switch (reason) {
+ case SkippedFrameReason::DrawingOff:
+ return "DrawingOff";
+ case SkippedFrameReason::ContextIsStopped:
+ return "ContextIsStopped";
+ case SkippedFrameReason::NothingToDraw:
+ return "NothingToDraw";
+ case SkippedFrameReason::NoOutputTarget:
+ return "NoOutputTarget";
+ case SkippedFrameReason::NoBuffer:
+ return "NoBuffer";
+ case SkippedFrameReason::AlreadyDrawn:
+ return "AlreadyDrawn";
+ }
+}
+
+void FrameInfo::setSkippedFrameReason(android::uirenderer::SkippedFrameReason reason) {
+ ATRACE_FORMAT_INSTANT("Frame skipped: %s", toString(reason));
+ addFlag(FrameInfoFlags::SkippedFrame);
+ mSkippedFrameReason = reason;
}
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index b15b6cb9a9ec..f7ad13978a30 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -16,15 +16,17 @@
#ifndef FRAMEINFO_H_
#define FRAMEINFO_H_
-#include "utils/Macros.h"
-
#include <cutils/compiler.h>
+#include <memory.h>
#include <utils/Timers.h>
#include <array>
-#include <memory.h>
+#include <optional>
#include <string>
+#include "SkippedFrameInfo.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -186,8 +188,14 @@ public:
return mFrameInfo[static_cast<int>(index)];
}
+ void setSkippedFrameReason(SkippedFrameReason reason);
+ inline std::optional<SkippedFrameReason> getSkippedFrameReason() const {
+ return mSkippedFrameReason;
+ }
+
private:
int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)];
+ std::optional<SkippedFrameReason> mSkippedFrameReason;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 687e4dd324d3..59f21694fb77 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -148,7 +148,7 @@ void FrameInfoVisualizer::initializeRects(const int baseline, const int width) {
int fast_i = 0, janky_i = 0;
// Set the bottom of all the shapes to the baseline
for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
float lineWidth = baseLineWidth;
@@ -181,7 +181,7 @@ void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex en
int janky_i = (mNumJankyRects - 1) * 4;
for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b7e99994355c..16de21def0e3 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,9 +22,11 @@
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrDirectContext.h>
+#include <GrTypes.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
#include <gui/TraceUtils.h>
@@ -262,8 +264,9 @@ private:
}
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
- mGrContext->submit(true);
+ SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
+ ahb);
+ mGrContext->submit(GrSyncCpu::kYes);
uploadSucceeded = (image.get() != nullptr);
});
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e75f4c..21f4ca79b68c 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@ namespace android::uirenderer {
constexpr static MemoryPolicy sDefaultMemoryPolicy;
constexpr static MemoryPolicy sPersistentOrSystemPolicy{
.contextTimeout = 10_s,
+ .minimumResourceRetention = 1_s,
+ .maximumResourceRetention = 10_s,
.useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
};
constexpr static MemoryPolicy sLowRamPolicy{
.useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34f52a..e10dda990dec 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@ struct MemoryPolicy {
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
+ // The maximum amount of time to hold onto items in the resource cache
+ nsecs_t maximumResourceRetention = 100000_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
bool useAlternativeUiHidden = true;
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 13e3c8e7bf77..69fda34afc78 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -19,6 +19,7 @@
#include <GrDirectContext.h>
#include <SkMesh.h>
+#include <include/gpu/ganesh/SkMeshGanesh.h>
#include <jni.h>
#include <log/log.h>
@@ -143,21 +144,34 @@ public:
}
if (mIsDirty || genId != mGenerationId) {
- auto vb = SkMesh::MakeVertexBuffer(
- context, reinterpret_cast<const void*>(mVertexBufferData.data()),
- mVertexBufferData.size());
+ auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+#ifdef __ANDROID__
+ auto vb = SkMeshes::MakeVertexBuffer(context,
+ vertexData,
+ mVertexBufferData.size());
+#else
+ auto vb = SkMeshes::MakeVertexBuffer(vertexData,
+ mVertexBufferData.size());
+#endif
auto meshMode = SkMesh::Mode(mMode);
if (!mIndexBufferData.empty()) {
- auto ib = SkMesh::MakeIndexBuffer(
- context, reinterpret_cast<const void*>(mIndexBufferData.data()),
- mIndexBufferData.size());
+ auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+#ifdef __ANDROID__
+ auto ib = SkMeshes::MakeIndexBuffer(context,
+ indexData,
+ mIndexBufferData.size());
+#else
+ auto ib = SkMeshes::MakeIndexBuffer(indexData,
+ mIndexBufferData.size());
+#endif
mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- mBounds)
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
.mesh;
} else {
mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, mBounds)
+ mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
.mesh;
}
mIsDirty = false;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 06aed63d8def..5e5eb4a51b35 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,7 +20,7 @@
#ifdef __ANDROID__
#include "HWUIProperties.sysprop.h"
#endif
-#include "SkTraceEventCommon.h"
+#include "src/core/SkTraceEventCommon.h"
#include <algorithm>
#include <cstdlib>
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 045de35c1d97..afe4c3896ed2 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -21,6 +21,7 @@
#include <SkCanvas.h>
#include <SkColorSpace.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -29,6 +30,7 @@
#include <SkSamplingOptions.h>
#include <SkSurface.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <gui/TraceUtils.h>
#include <private/android/AHardwareBufferHelpers.h>
#include <shaders/shaders.h>
@@ -108,7 +110,8 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy
sk_sp<SkColorSpace> colorSpace =
DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
+ SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType,
+ colorSpace);
if (!image.get()) {
return request->onCopyFinished(CopyResult::UnknownError);
@@ -171,16 +174,16 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy
SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
SkBitmap* bitmap = &skBitmap;
sk_sp<SkSurface> tmpSurface =
- SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
- bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
+ bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return request->onCopyFinished(CopyResult::UnknownError);
@@ -346,19 +349,19 @@ bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect*
* a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
* software buffer.
*/
- sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- bitmap->info(),
- 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ bitmap->info(),
+ 0,
+ kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 924fbd659824..ff0d8d74831c 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
#include "SkImageFilter.h"
#include "SkImageInfo.h"
#include "SkLatticeIter.h"
+#include "SkMesh.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRRect.h"
@@ -49,6 +50,7 @@
#include "effects/GainmapRenderer.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "include/gpu/GrDirectContext.h"
+#include "include/gpu/ganesh/SkMeshGanesh.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
#ifdef __ANDROID__
@@ -107,11 +109,6 @@ struct Op {
};
static_assert(sizeof(Op) == 4, "");
-struct Flush final : Op {
- static const auto kType = Type::Flush;
- void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
struct Save final : Op {
static const auto kType = Type::Save;
void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -532,24 +529,26 @@ struct DrawSkMesh final : Op {
mutable bool isGpuBased;
mutable GrDirectContext::DirectContextID contextId;
void draw(SkCanvas* c, const SkMatrix&) const {
+#ifdef __ANDROID__
GrDirectContext* directContext = c->recordingContext()->asDirectContext();
GrDirectContext::DirectContextID id = directContext->directContextID();
if (!isGpuBased || contextId != id) {
sk_sp<SkMesh::VertexBuffer> vb =
- SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+ SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
if (!cpuMesh.indexBuffer()) {
gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
- cpuMesh.bounds())
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
} else {
sk_sp<SkMesh::IndexBuffer> ib =
- SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+ SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
cpuMesh.indexCount(), cpuMesh.indexOffset(),
- cpuMesh.refUniforms(), cpuMesh.bounds())
+ cpuMesh.refUniforms(),
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
}
@@ -558,6 +557,9 @@ struct DrawSkMesh final : Op {
}
c->drawMesh(gpuMesh, blender, paint);
+#else
+ c->drawMesh(cpuMesh, blender, paint);
+#endif
}
};
@@ -675,12 +677,11 @@ public:
// because the webview functor still doesn't respect the canvas clip stack.
const SkIRect deviceBounds = c->getDeviceClipBounds();
if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) {
- GrRecordingContext* directContext = c->recordingContext();
mLayerImageInfo =
c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
- mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
- mLayerImageInfo, 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ // SkCanvas::makeSurface returns a new surface that will be GPU-backed if
+ // canvas was also.
+ mLayerSurface = c->makeSurface(mLayerImageInfo);
}
SkCanvas* layerCanvas = mLayerSurface->getCanvas();
@@ -752,10 +753,6 @@ inline void DisplayListData::map(const Fn fns[], Args... args) const {
}
}
-void DisplayListData::flush() {
- this->push<Flush>(0);
-}
-
void DisplayListData::save() {
this->push<Save>(0);
}
@@ -1047,10 +1044,6 @@ sk_sp<SkSurface> RecordingCanvas::onNewSurface(const SkImageInfo&, const SkSurfa
return nullptr;
}
-void RecordingCanvas::onFlush() {
- fDL->flush();
-}
-
void RecordingCanvas::willSave() {
mSaveCount++;
fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d6d557..4f54ee286a56 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -127,8 +127,6 @@ public:
private:
friend class RecordingCanvas;
- void flush();
-
void save();
void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
void saveBehind(const SkRect*);
@@ -208,8 +206,6 @@ public:
void willRestore() override;
bool onDoSaveBehind(const SkRect*) override;
- void onFlush() override;
-
void didConcat44(const SkM44&) override;
void didSetM44(const SkM44&) override;
void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 1c39db3a31bb..d28bb499c907 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -28,16 +28,20 @@
#include "DamageAccumulator.h"
#include "pipeline/skia/SkiaDisplayList.h"
#endif
+#include <SkPathOps.h>
#include <gui/TraceUtils.h>
-#include "utils/MathUtils.h"
-#include "utils/StringUtils.h"
+#include <ui/FatVector.h>
-#include <SkPathOps.h>
#include <algorithm>
#include <atomic>
#include <sstream>
#include <string>
-#include <ui/FatVector.h>
+
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
namespace android {
namespace uirenderer {
@@ -109,6 +113,13 @@ void RenderNode::output(std::ostream& output, uint32_t level) {
output << std::endl;
}
+void RenderNode::visit(std::function<void(const RenderNode&)> func) const {
+ func(*this);
+ if (mDisplayList) {
+ mDisplayList.visit(func);
+ }
+}
+
int RenderNode::getUsageSize() {
int size = sizeof(RenderNode);
size += mStagingDisplayList.getUsedSize();
@@ -260,6 +271,12 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu
pushStagingDisplayListChanges(observer, info);
}
+ // always damageSelf when filtering backdrop content, or else the BackdropFilterDrawable will
+ // get a wrong snapshot of previous content.
+ if (mProperties.layerProperties().getBackdropImageFilter()) {
+ damageSelf(info);
+ }
+
if (mDisplayList) {
info.out.hasFunctors |= mDisplayList.hasFunctor();
mHasHolePunches = mDisplayList.hasHolePunches();
@@ -357,13 +374,18 @@ std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired(
mImageFilterClipBounds != clipBounds ||
mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
- mSnapshotResult.snapshot =
- snapshot->makeWithFilter(context,
- imageFilter,
- subset,
- clipBounds,
- &mSnapshotResult.outSubset,
- &mSnapshotResult.outOffset);
+#ifdef __ANDROID__
+ if (context) {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ context, snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ } else
+#endif
+ {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ }
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 572635a9bd45..c959db37474b 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -129,10 +129,6 @@ public:
StretchMask& getStretchMask() { return mStretchMask; }
- VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
-
- void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
-
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -215,6 +211,8 @@ public:
void output(std::ostream& output, uint32_t level);
+ void visit(std::function<void(const RenderNode&)>) const;
+
void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; }
UsageHint usageHint() const { return mUsageHint; }
@@ -222,6 +220,7 @@ public:
int64_t uniqueId() const { return mUniqueId; }
void setIsTextureView() { mIsTextureView = true; }
+ bool isTextureView() const { return mIsTextureView; }
void markDrawStart(SkCanvas& canvas);
void markDrawEnd(SkCanvas& canvas);
@@ -248,7 +247,6 @@ private:
const int64_t mUniqueId;
String8 mName;
- sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 0589f136b666..c5371236b9cf 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -55,6 +55,12 @@ bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) {
return true;
}
+bool LayerProperties::setBackdropImageFilter(SkImageFilter* imageFilter) {
+ if (mBackdropImageFilter.get() == imageFilter) return false;
+ mBackdropImageFilter = sk_ref_sp(imageFilter);
+ return true;
+}
+
bool LayerProperties::setFromPaint(const SkPaint* paint) {
bool changed = false;
changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint)));
@@ -70,6 +76,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
setXferMode(other.xferMode());
setColorFilter(other.getColorFilter());
setImageFilter(other.getImageFilter());
+ setBackdropImageFilter(other.getBackdropImageFilter());
mStretchEffect = other.mStretchEffect;
return *this;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 064ba7aee107..e358b57f6fe1 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -97,8 +97,12 @@ public:
bool setImageFilter(SkImageFilter* imageFilter);
+ bool setBackdropImageFilter(SkImageFilter* imageFilter);
+
SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
+ SkImageFilter* getBackdropImageFilter() const { return mBackdropImageFilter.get(); }
+
const StretchEffect& getStretchEffect() const { return mStretchEffect; }
StretchEffect& mutableStretchEffect() { return mStretchEffect; }
@@ -129,6 +133,7 @@ private:
SkBlendMode mMode;
sk_sp<SkColorFilter> mColorFilter;
sk_sp<SkImageFilter> mImageFilter;
+ sk_sp<SkImageFilter> mBackdropImageFilter;
StretchEffect mStretchEffect;
};
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8394c3cd4175..31fc929dfcdf 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
#include <utility>
#include "CanvasProperty.h"
+#include "FeatureFlags.h"
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
@@ -795,7 +796,9 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai
sk_sp<SkTextBlob> textBlob(builder.make());
applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
- drawTextDecorations(x, y, totalAdvance, paintCopy);
+ if (!text_feature::fix_double_underline()) {
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
+ }
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index b785989f35cb..ced02241ffe2 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -175,7 +175,7 @@ protected:
const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
- void onFilterPaint(Paint& paint);
+ virtual void onFilterPaint(Paint& paint);
Paint filterPaint(const Paint& src) {
Paint dst(src);
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index b58f517834a3..c67b135855f7 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -18,9 +18,8 @@
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
-#include "include/private/SkFixed.h"
-#include "src/core/SkTSearch.h"
+#include <cstdlib>
#include <log/log.h>
typedef int Dot14;
@@ -41,18 +40,18 @@ static inline Dot14 pin_and_convert(float x) {
if (x <= 0) {
return 0;
}
- if (x >= SK_Scalar1) {
+ if (x >= 1.0f) {
return Dot14_ONE;
}
- return SkScalarToFixed(x) >> 2;
+ return static_cast<Dot14>(x * Dot14_ONE);
}
static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) {
// pin to the unit-square, and convert to 2.14
Dot14 x = pin_and_convert(value);
- if (x == 0) return 0;
- if (x == Dot14_ONE) return SK_Scalar1;
+ if (x == 0) return 0.0f;
+ if (x == Dot14_ONE) return 1.0f;
Dot14 b = pin_and_convert(bx);
Dot14 c = pin_and_convert(cx);
@@ -84,7 +83,7 @@ static float SkUnitCubicInterp(float value, float bx, float by, float cx, float
A = 3 * b;
B = 3 * (c - 2 * b);
C = 3 * (b - c) + Dot14_ONE;
- return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+ return Dot14ToFloat(eval_cubic(t, A, B, C));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,7 +103,7 @@ void SkiaInterpolatorBase::reset(int elemCount, int frameCount) {
fFlags = 0;
fElemCount = static_cast<uint8_t>(elemCount);
fFrameCount = static_cast<int16_t>(frameCount);
- fRepeat = SK_Scalar1;
+ fRepeat = 1.0f;
if (fStorage) {
free(fStorage);
fStorage = nullptr;
@@ -136,17 +135,46 @@ bool SkiaInterpolatorBase::getDuration(SkMSec* startTime, SkMSec* endTime) const
float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime,
const float blend[4]) {
- SkASSERT(time > prevTime && time < nextTime);
+ LOG_FATAL_IF(time < prevTime || time > nextTime);
float t = (float)(time - prevTime) / (float)(nextTime - prevTime);
return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
}
+// Returns the index of where the item is or the bit not of the index
+// where the item should go in order to keep arr sorted in ascending order.
+int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) {
+ if (count <= 0) {
+ return ~0;
+ }
+
+ int lo = 0;
+ int hi = count - 1;
+
+ while (lo < hi) {
+ int mid = (hi + lo) / 2;
+ SkMSec elem = arr[mid].fTime;
+ if (elem == target) {
+ return mid;
+ } else if (elem < target) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ // Check to see if target is greater or less than where we stopped
+ if (target < arr[lo].fTime) {
+ return ~lo;
+ }
+ // e.g. it should go at the end.
+ return ~(lo + 1);
+}
+
SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr,
bool* exactPtr) const {
- SkASSERT(fFrameCount > 0);
+ LOG_FATAL_IF(fFrameCount <= 0);
Result result = kNormal_Result;
- if (fRepeat != SK_Scalar1) {
+ if (fRepeat != 1.0f) {
SkMSec startTime = 0, endTime = 0; // initialize to avoid warning
this->getDuration(&startTime, &endTime);
SkMSec totalTime = endTime - startTime;
@@ -168,10 +196,8 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T
time = offsetTime + startTime;
}
- int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode));
-
+ int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time);
bool exact = true;
-
if (index < 0) {
index = ~index;
if (index == 0) {
@@ -184,10 +210,11 @@ SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T
}
result = kFreezeEnd_Result;
} else {
+ // Need to interpolate between two frames.
exact = false;
}
}
- SkASSERT(index < fFrameCount);
+ LOG_FATAL_IF(index >= fFrameCount);
const SkTimeCode* nextTime = &fTimes[index];
SkMSec nextT = nextTime[0].fTime;
if (exact) {
@@ -207,7 +234,7 @@ SkiaInterpolator::SkiaInterpolator() {
}
SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
- SkASSERT(elemCount > 0);
+ LOG_FATAL_IF(elemCount <= 0);
this->reset(elemCount, frameCount);
}
@@ -221,21 +248,19 @@ void SkiaInterpolator::reset(int elemCount, int frameCount) {
fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
}
-#define SK_Fixed1Third (SK_Fixed1 / 3)
-#define SK_Fixed2Third (SK_Fixed1 * 2 / 3)
-
static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f};
bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[],
const float blend[4]) {
- SkASSERT(values != nullptr);
+ LOG_FATAL_IF(values == nullptr);
if (blend == nullptr) {
blend = gIdentityBlend;
}
- bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
- SkASSERT(success);
+ // Verify the time should go after all the frames before index
+ bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time);
+ LOG_FATAL_IF(!success);
if (success) {
SkTimeCode* timeCode = &fTimes[index];
timeCode->fTime = time;
@@ -257,7 +282,7 @@ SkiaInterpolator::Result SkiaInterpolator::timeToValues(SkMSec time, float value
if (exact) {
memcpy(values, nextSrc, fElemCount * sizeof(float));
} else {
- SkASSERT(index > 0);
+ LOG_FATAL_IF(index <= 0);
const float* prevSrc = nextSrc - fElemCount;
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index 9422cb526a8f..62e6c1e33e40 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -68,14 +68,16 @@ protected:
enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 };
static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime,
const float blend[4] = nullptr);
- int16_t fFrameCount;
- uint8_t fElemCount;
- uint8_t fFlags;
- float fRepeat;
struct SkTimeCode {
uint32_t fTime;
float fBlend[4];
};
+ static int binarySearch(const SkTimeCode* arr, int count, uint32_t target);
+
+ int16_t fFrameCount;
+ uint8_t fElemCount;
+ uint8_t fFlags;
+ float fRepeat;
SkTimeCode* fTimes; // pointer into fStorage
void* fStorage;
};
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
new file mode 100644
index 000000000000..bd0e35aadbb4
--- /dev/null
+++ b/libs/hwui/SkiaWrapper.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SKIA_WRAPPER_H_
+#define SKIA_WRAPPER_H_
+
+#include <SkRefCnt.h>
+#include <utils/RefBase.h>
+
+namespace android::uirenderer {
+
+template <typename T>
+class SkiaWrapper : public VirtualLightRefBase {
+public:
+ sk_sp<T> getInstance() {
+ if (mInstance != nullptr && shouldDiscardInstance()) {
+ mInstance = nullptr;
+ }
+
+ if (mInstance == nullptr) {
+ mInstance = createInstance();
+ mGenerationId++;
+ }
+ return mInstance;
+ }
+
+ virtual bool shouldDiscardInstance() const { return false; }
+
+ void discardInstance() { mInstance = nullptr; }
+
+ [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
+protected:
+ virtual sk_sp<T> createInstance() = 0;
+
+private:
+ sk_sp<T> mInstance = nullptr;
+ int32_t mGenerationId = 0;
+};
+
+} // namespace android::uirenderer
+
+#endif // SKIA_WRAPPER_H_
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/hwui/SkippedFrameInfo.h
index abc6b9f9a746..de56d9a26982 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
+++ b/libs/hwui/SkippedFrameInfo.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.bubble
+#pragma once
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+namespace android::uirenderer {
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
- ChangeActiveActivityFromBubbleTest(flicker)
+enum class SkippedFrameReason {
+ DrawingOff,
+ ContextIsStopped,
+ NothingToDraw,
+ NoOutputTarget,
+ NoBuffer,
+ AlreadyDrawn,
+};
+
+} /* namespace android::uirenderer */
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 974a5d05aa84..ae29edf535a2 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
// libshaders only exists on Android devices
#ifdef __ANDROID__
+#include <renderthread/CanvasContext.h>
#include <shaders/shaders.h>
#endif
@@ -53,8 +54,17 @@ static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearE
ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+ auto colorTransform = android::mat4();
+ const auto* context = renderthread::CanvasContext::getActiveContext();
+ if (context) {
+ const auto ratio = context->targetSdrHdrRatio();
+ if (ratio > 1.0f) {
+ colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
+ }
+ }
+
const auto uniforms =
- shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
currentDisplayLuminanceNits, maxLuminance);
for (const auto& uniform : uniforms) {
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2bff9cb74fa7..ea25f68d7170 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,14 +16,16 @@
#pragma once
-#include "Properties.h"
-#include "utils/Macros.h"
-
#include <utils/Timers.h>
-#include "SkSize.h"
+#include <optional>
#include <string>
+#include "Properties.h"
+#include "SkSize.h"
+#include "SkippedFrameInfo.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -110,13 +112,13 @@ public:
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
bool requiresUiRedraw = false;
- // This is set to true if draw() can be called this frame
- // false means that we must delay until the next vsync pulse as frame
+ // This is set to nullopt if draw() can be called this frame
+ // A value means that we must delay until the next vsync pulse as frame
// production is outrunning consumption
- // NOTE that if this is false CanvasContext will set either requiresUiRedraw
+ // NOTE that if this has a value CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
- bool canDrawThisFrame = true;
+ std::optional<SkippedFrameReason> skippedFrameReason;
// Sentinel for animatedImageDelay meaning there is no need to post such
// a message.
static constexpr nsecs_t kNoAnimatedImageDelay = -1;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
new file mode 100644
index 000000000000..78a64795967a
--- /dev/null
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -0,0 +1,43 @@
+package: "com.android.graphics.hwui.flags"
+
+flag {
+ name: "limited_hdr"
+ namespace: "core_graphics"
+ description: "API to enable apps to restrict the amount of HDR headroom that is used"
+ bug: "234181960"
+}
+
+flag {
+ name: "hdr_10bit_plus"
+ namespace: "core_graphics"
+ description: "Use 10101010 and FP16 formats for HDR-UI when available"
+ bug: "284159488"
+}
+
+flag {
+ name: "gainmap_animations"
+ namespace: "core_graphics"
+ description: "APIs to help enable animations involving gainmaps"
+ bug: "296482289"
+}
+
+flag {
+ name: "gainmap_constructor_with_metadata"
+ namespace: "core_graphics"
+ description: "APIs to create a new gainmap with a bitmap for metadata."
+ bug: "304478551"
+}
+
+flag {
+ name: "clip_surfaceviews"
+ namespace: "core_graphics"
+ description: "Clip z-above surfaceviews to global clip rect"
+ bug: "298621623"
+}
+
+flag {
+ name: "requested_formats_v"
+ namespace: "core_graphics"
+ description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
+ bug: "292545615"
+}
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b1d17c..c80a9b4ae97f 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
#include <log/log.h>
#include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 905b123076a2..19f726a31b33 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -45,9 +45,9 @@ static bool convert(const ANativeWindow_Buffer* buffer,
SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
- // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to
+ // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to
// draw into the canvas.
- sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
+ sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes);
if (surface.get() != nullptr) {
if (outBitmap) {
outBitmap->setInfo(imageInfo, rowBytes);
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e78fe23..883f273b5d3d 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
#include <sys/cdefs.h>
#include <vulkan/vulkan.h>
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 651d526511ef..3ebf7d19202d 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -244,11 +244,18 @@ private:
// This can happen if a BitmapShader is used on multiple canvas', such as a
// software + hardware canvas, which is otherwise valid as SkShader is "immutable"
std::lock_guard _lock(mUniformGuard);
- const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
- (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr));
- const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ // Compute the weight parameter that will be used to blend between the images.
+ float W = 0.f;
+ if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+ if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+ W = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ } else {
+ W = 1.f;
+ }
+ }
mBuilder.uniform("W") = W;
uniforms = mBuilder.uniforms();
}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 8049dc946c9e..27773a60355a 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -111,7 +111,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
{
std::unique_lock lock{mImageLock};
snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
}
return snap;
@@ -123,7 +123,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
snap.mDurationMS = currentFrameDuration();
}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 92d875bf7f1e..8344a86923ee 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -43,12 +43,15 @@
#include <SkColor.h>
#include <SkEncodedImageFormat.h>
#include <SkHighContrastFilter.h>
-#include <SkImageEncoder.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImagePriv.h>
#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
#include <SkRect.h>
#include <SkStream.h>
+#include <SkJpegEncoder.h>
+#include <SkPngEncoder.h>
#include <SkWebpEncoder.h>
#include <limits>
@@ -296,7 +299,8 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes
mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
AHardwareBuffer_acquire(buffer);
setImmutable(); // HW bitmaps are always immutable
- mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace());
+ mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
+ mInfo.refColorSpace());
}
#endif
@@ -407,7 +411,12 @@ sk_sp<SkImage> Bitmap::makeImage() {
// Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
// internally and ~Bitmap won't be invoked.
// TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+#ifdef __ANDROID__
+ // pinnable images are only supported with the Ganesh GPU backend compiled in.
+ image = SkImages::PinnableRasterFromBitmap(skiaBitmap);
+#else
image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+#endif
}
return image;
}
@@ -528,17 +537,25 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format,
return false;
}
- SkEncodedImageFormat fm;
switch (format) {
- case JavaCompressFormat::Jpeg:
- fm = SkEncodedImageFormat::kJPEG;
- break;
+ case JavaCompressFormat::Jpeg: {
+ SkJpegEncoder::Options options;
+ options.fQuality = quality;
+ return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::Png:
- fm = SkEncodedImageFormat::kPNG;
- break;
- case JavaCompressFormat::Webp:
- fm = SkEncodedImageFormat::kWEBP;
- break;
+ return SkPngEncoder::Encode(stream, bitmap.pixmap(), {});
+ case JavaCompressFormat::Webp: {
+ SkWebpEncoder::Options options;
+ if (quality >= 100) {
+ options.fCompression = SkWebpEncoder::Compression::kLossless;
+ options.fQuality = 75; // This is effort to compress
+ } else {
+ options.fCompression = SkWebpEncoder::Compression::kLossy;
+ options.fQuality = quality;
+ }
+ return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::WebpLossy:
case JavaCompressFormat::WebpLossless: {
SkWebpEncoder::Options options;
@@ -548,8 +565,6 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format,
return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
}
}
-
- return SkEncodeImage(stream, bitmap, fm, quality);
}
sp<uirenderer::Gainmap> Bitmap::gainmap() const {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index cd8af3d933b1..80b6c0385fca 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
#include "Canvas.h"
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "RenderNode.h"
#include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
#include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
namespace android {
@@ -34,13 +35,6 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
- const Paint& paint, Canvas* canvas) {
- const SkScalar strokeWidth = fmax(thickness, 1.0f);
- const SkScalar bottom = top + strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
-}
-
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
// paint has already been filtered by our caller, so we can ignore any filter
const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa
}
}
-static void simplifyPaint(int color, Paint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
- : layout(layout)
- , canvas(canvas)
- , paint(paint)
- , x(x)
- , y(y)
- , totalAdvance(totalAdvance) {}
-
- void operator()(size_t start, size_t end) {
- auto glyphFunc = [&](uint16_t* text, float* positions) {
- for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
- text[textIndex++] = layout.getGlyphId(i);
- positions[posIndex++] = x + layout.getX(i);
- positions[posIndex++] = y + layout.getY(i);
- }
- };
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
- Paint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
- // inner
- gDrawTextBlobMode = DrawTextBlobMode::HctInner;
- Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
- gDrawTextBlobMode = DrawTextBlobMode::Normal;
- } else {
- // standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
- }
- }
-
-private:
- const minikin::Layout& layout;
- Canvas* canvas;
- const Paint& paint;
- float x;
- float y;
- float totalAdvance;
-};
-
void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
int glyphCount, const Paint& paint) {
// Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -151,7 +78,7 @@ void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const fl
memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
};
- const minikin::MinikinFont* minikinFont = font.typeface().get();
+ const minikin::MinikinFont* minikinFont = font.baseTypeface().get();
SkFont* skfont = &copied.getSkFont();
MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
@@ -182,6 +109,31 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count,
DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
+
+ if (text_feature::fix_double_underline()) {
+ Paint copied(paint);
+ PaintFilter* filter = getPaintFilter();
+ if (filter != nullptr) {
+ filter->filterFullPaint(&copied);
+ }
+ const bool isUnderline = copied.isUnderline();
+ const bool isStrikeThru = copied.isStrikeThru();
+ if (isUnderline || isStrikeThru) {
+ const SkScalar left = x;
+ const SkScalar right = x + layout.getAdvance();
+ if (isUnderline) {
+ const SkScalar top = y + f.getUnderlinePosition();
+ drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+ }
+ if (isStrikeThru) {
+ float textSize = paint.getSkFont().getSize();
+ const float position = textSize * Paint::kStdStrikeThru_Top;
+ const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+ const SkScalar top = y + position;
+ drawStroke(left, right, top, thickness, copied, this);
+ }
+ }
+ }
}
void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d34d23..9ec023b2c36f 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@ protected:
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y,float totalAdvance) = 0;
+ float y, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 000000000000..2e6e97634aec
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+ const Paint& paint, Canvas* canvas) {
+ const SkScalar strokeWidth = fmax(thickness, 1.0f);
+ const SkScalar bottom = top + strokeWidth;
+ canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+ float y, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , totalAdvance(totalAdvance)
+ , underlinePosition(0)
+ , underlineThickness(0) {}
+
+ void operator()(size_t start, size_t end) {
+ auto glyphFunc = [&](uint16_t* text, float* positions) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ };
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+ Paint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+ // inner
+ gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+ Paint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+ gDrawTextBlobMode = DrawTextBlobMode::Normal;
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+ }
+
+ if (text_feature::fix_double_underline()) {
+ // Extract underline position and thickness.
+ if (paint.isUnderline()) {
+ SkFontMetrics metrics;
+ paint.getSkFont().getMetrics(&metrics);
+ const float textSize = paint.getSkFont().getSize();
+ SkScalar position;
+ if (!metrics.hasUnderlinePosition(&position)) {
+ position = textSize * Paint::kStdUnderline_Top;
+ }
+ SkScalar thickness;
+ if (!metrics.hasUnderlineThickness(&thickness)) {
+ thickness = textSize * Paint::kStdUnderline_Thickness;
+ }
+
+ // If multiple fonts are used, use the most bottom position and most thick stroke
+ // width as the underline position. This follows the CSS standard:
+ // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+ // <quote>
+ // The exact position and thickness of line decorations is UA-defined in this level.
+ // However, for underlines and overlines the UA must use a single thickness and
+ // position on each line for the decorations deriving from a single decorating box.
+ // </quote>
+ underlinePosition = std::max(underlinePosition, position);
+ underlineThickness = std::max(underlineThickness, thickness);
+ }
+ }
+ }
+
+ float getUnderlinePosition() const { return underlinePosition; }
+ float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+ const minikin::Layout& layout;
+ Canvas* canvas;
+ const Paint& paint;
+ float x;
+ float y;
+ float totalAdvance;
+ float underlinePosition;
+ float underlineThickness;
+};
+
+} // namespace android
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f0cce4..588463c49497 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
#include <memory>
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e359145feef7..7552b56d2ad6 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
#include "MinikinUtils.h"
-#include <string>
-
#include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
#include <minikin/MeasuredText.h>
#include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
#include "Paint.h"
#include "SkPathMeasure.h"
#include "Typeface.h"
@@ -43,9 +46,17 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
minikinPaint.wordSpacing = paint->getWordSpacing();
minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
- minikinPaint.familyVariant = paint->getFamilyVariant();
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+ const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ minikinPaint.familyVariant = familyVariant.value();
+ } else {
+ minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+ ? minikin::FamilyVariant::ELEGANT
+ : minikin::FamilyVariant::DEFAULT;
+ }
return minikinPaint;
}
@@ -84,7 +95,8 @@ void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const
float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
- size_t count, size_t bufSize, float* advances) {
+ size_t count, size_t bufSize, float* advances,
+ minikin::MinikinRect* bounds) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
const minikin::U16StringPiece textBuf(buf, bufSize);
const minikin::Range range(start, start + count);
@@ -92,7 +104,7 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances);
+ endHyphen, advances, bounds);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 009b84b140ea..61bc881faa54 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -51,10 +51,9 @@ public:
static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
- static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
- const Typeface* typeface, const uint16_t* buf,
- size_t start, size_t count, size_t bufSize,
- float* advances);
+ static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ float* advances, minikin::MinikinRect* bounds);
static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
@@ -76,7 +75,7 @@ public:
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
if (i > 0 && nextFont != curFont) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e10fc26..caffdfc907f7 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -94,9 +94,10 @@ public:
uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
+ void resetFamilyVariant() { mFamilyVariant.reset(); }
void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
- minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+ std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
void setStartHyphenEdit(uint32_t startHyphen) {
mHyphenEdit = minikin::packHyphenEdit(
@@ -171,7 +172,7 @@ private:
float mWordSpacing = 0;
std::string mFontFeatureSettings;
uint32_t mMinikinLocaleListId;
- minikin::FamilyVariant mFamilyVariant;
+ std::optional<minikin::FamilyVariant> mFamilyVariant;
uint32_t mHyphenEdit = 0;
// The native Typeface object has the same lifetime of the Java Typeface
// object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 3c67edc9a428..b63ee1bd3d98 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -140,9 +140,8 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font
const minikin::FontStyle defaultStyle;
const minikin::MinikinFont* mf =
- families.empty()
- ? nullptr
- : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+ families.empty() ? nullptr
+ : families[0]->getClosestMatch(defaultStyle).typeface().get();
if (mf != nullptr) {
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
const SkFontStyle& style = skTypeface->fontStyle();
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index a7f5aa83e624..90b1da846205 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
-#include "ImageDecoder.h"
-#include "Utils.h"
-
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
@@ -27,10 +23,15 @@
#include <SkRect.h>
#include <SkRefCnt.h>
#include <hwui/AnimatedImageDrawable.h>
-#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
+#include <hwui/ImageDecoder.h>
#include <utils/Looper.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "Utils.h"
+
using namespace android;
static jclass gAnimatedImageDrawableClass;
@@ -145,8 +146,9 @@ static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlo
static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jlong nativeFilter) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
- drawable->setStagingColorFilter(sk_ref_sp(filter));
+ auto filter = uirenderer::ColorFilter::fromJava(nativeFilter);
+ auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>();
+ drawable->setStagingColorFilter(skColorFilter);
}
static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576651f2..9e21f860ce21 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
// #define LOG_NDEBUG 0
#include "Bitmap.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a59122..3d0a53440bfb 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
#include "BitmapFactory.h"
#include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f77270..ea5c14486ea4 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
#include "BitmapRegionDecoder.h"
#include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 4bd7ef47b871..0b95148d3c82 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -15,20 +15,21 @@
** limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "SkBlendMode.h"
-#include "SkColorFilter.h"
-#include "SkColorMatrixFilter.h"
namespace android {
using namespace uirenderer;
-class SkColorFilterGlue {
+class ColorFilterGlue {
public:
- static void SafeUnref(SkColorFilter* filter) {
- SkSafeUnref(filter);
+ static void SafeUnref(ColorFilter* filter) {
+ if (filter) {
+ filter->decStrong(nullptr);
+ }
}
static jlong GetNativeFinalizer(JNIEnv*, jobject) {
@@ -36,41 +37,75 @@ public:
}
static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
- SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
- return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release());
+ auto mode = static_cast<SkBlendMode>(modeHandle);
+ auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode);
+ blendModeFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter));
}
static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
+ auto* lightingFilter = new LightingFilter(mul, add);
+ lightingFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter));
}
- static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
- float matrix[20];
- env->GetFloatArrayRegion(jarray, 0, 20, matrix);
+ static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setMul(mul);
+ }
+ }
+
+ static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setAdd(add);
+ }
+ }
+
+ static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) {
+ std::vector<float> matrix(20);
+ // float matrix[20];
+ env->GetFloatArrayRegion(jarray, 0, 20, matrix.data());
// java biases the translates by 255, so undo that before calling skia
matrix[ 4] *= (1.0f/255);
matrix[ 9] *= (1.0f/255);
matrix[14] *= (1.0f/255);
matrix[19] *= (1.0f/255);
- return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release());
+ return matrix;
+ }
+
+ static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+ std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray);
+ auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix));
+ colorMatrixColorFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter));
+ }
+
+ static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr,
+ jfloatArray jarray) {
+ auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr);
+ if (filter) {
+ filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
+ }
}
};
static const JNINativeMethod colorfilter_methods[] = {
- {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
-};
+ {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}};
static const JNINativeMethod blendmode_methods[] = {
- { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
+ {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter},
};
static const JNINativeMethod lighting_methods[] = {
- { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter },
-};
+ {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter},
+ {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd},
+ {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}};
static const JNINativeMethod colormatrix_methods[] = {
- { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter },
-};
+ {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
+ {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
int register_android_graphics_ColorFilter(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 15e529e169fc..a66d3b860ade 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -1,11 +1,11 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkData.h"
-#include "SkMalloc.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
#include "Utils.h"
+#include <cstdlib>
#include <nativehelper/JNIHelp.h>
#include <log/log.h>
#include <memory>
@@ -177,6 +177,10 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray s
return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
}
+static void free_pointer_skproc(const void* ptr, void*) {
+ free((void*)ptr);
+}
+
sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
if (!stream) {
@@ -186,19 +190,31 @@ sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray s
size_t bufferSize = 4096;
size_t streamLen = 0;
size_t len;
- char* data = (char*)sk_malloc_throw(bufferSize);
+ char* data = (char*)malloc(bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
while ((len = stream->read(data + streamLen,
bufferSize - streamLen)) != 0) {
streamLen += len;
if (streamLen == bufferSize) {
bufferSize *= 2;
- data = (char*)sk_realloc_throw(data, bufferSize);
+ data = (char*)realloc(data, bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
}
}
- data = (char*)sk_realloc_throw(data, streamLen);
-
- return SkData::MakeFromMalloc(data, streamLen);
+ if (streamLen == 0) {
+ // realloc with size 0 is unspecified behavior in C++11
+ free(data);
+ data = nullptr;
+ } else {
+ // Trim down the buffer to the actual size of the data.
+ LOG_FATAL_IF(streamLen > bufferSize);
+ data = (char*)realloc(data, streamLen);
+ LOG_ALWAYS_FATAL_IF(!data);
+ }
+ // Just in case sk_free differs from free, we ask Skia to use
+ // free to cleanup the buffer that SkData wraps.
+ return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 69774cc6e133..0c3af61fc089 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include "FontUtils.h"
@@ -89,7 +86,8 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) {
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
builder->langId, builder->variant, std::move(builder->fonts),
- true /* isCustomFallback */, false /* isDefaultFallback */);
+ true /* isCustomFallback */, false /* isDefaultFallback */,
+ minikin::VariationFamilyType::None);
if (family->getCoverage().length() == 0) {
return 0;
}
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7ee247..0fffee744be0 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@ static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobj
p.writeFloat(info.fDisplayRatioHdr);
// base image type
p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
- // type
- p.writeInt32(static_cast<int32_t>(info.fType));
#else
doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
@@ -232,7 +230,6 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job
info.fDisplayRatioSdr = p.readFloat();
info.fDisplayRatioHdr = p.readFloat();
info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
- info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
fromJava(nativeObject)->info = info;
#else
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b7654d..7cc48661619a 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
#include <assert.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 23ab5dd38b1a..b9fff36d372e 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -125,14 +125,6 @@ public:
static jobject createBitmapRegionDecoder(JNIEnv* env,
android::BitmapRegionDecoderWrapper* bitmap);
- /**
- * Given a bitmap we natively allocate a memory block to store the contents
- * of that bitmap. The memory is then attached to the bitmap via an
- * SkPixelRef, which ensures that upon deletion the appropriate caches
- * are notified.
- */
- static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap);
-
/** Copy the colors in colors[] to the bitmap, convert to the correct
format along the way.
Whether to use premultiplied pixels is determined by dstBitmap's alphaType.
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c9115483c..54369b9e4384 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
#include <JankTracker.h>
#include <log/log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 048ce025ce27..cbd452031f69 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -1,6 +1,5 @@
#include "GraphicsJNI.h"
#include "SkMaskFilter.h"
-#include "SkBlurMask.h"
#include "SkBlurMaskFilter.h"
#include "SkBlurTypes.h"
#include "SkTableMaskFilter.h"
@@ -11,6 +10,13 @@ static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
}
}
+// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28
+static constexpr float kBLUR_SIGMA_SCALE = 0.57735f;
+
+static float convertRadiusToSigma(float radius) {
+ return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
class SkMaskFilterGlue {
public:
static void destructor(JNIEnv* env, jobject, jlong filterHandle) {
@@ -19,7 +25,7 @@ public:
}
static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release();
ThrowIAE_IfNull(env, filter);
return reinterpret_cast<jlong>(filter);
@@ -34,7 +40,7 @@ public:
direction[i] = values[i];
}
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma,
direction, ambient, specular).release();
ThrowIAE_IfNull(env, filter);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a22b5cb..67ef143e6179 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "9patch"
#define LOG_NDEBUG 1
#include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 13357fa25e8c..8c71d6fc7860 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,16 +15,29 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
-#include <utils/Log.h>
-
-#include "GraphicsJNI.h"
+#include <hwui/BlurDrawLooper.h>
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
+#include <minikin/Measurement.h>
+#include <minikin/MinikinPaint.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
+#include <unicode/utf16.h>
+#include <utils/Log.h>
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <vector>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
#include "SkColorFilter.h"
#include "SkColorSpace.h"
#include "SkFont.h"
@@ -35,27 +48,21 @@
#include "SkPathEffect.h"
#include "SkPathUtils.h"
#include "SkShader.h"
-#include "SkBlendMode.h"
#include "unicode/uloc.h"
#include "utils/Blur.h"
-#include <hwui/BlurDrawLooper.h>
-#include <hwui/MinikinSkia.h>
-#include <hwui/MinikinUtils.h>
-#include <hwui/Paint.h>
-#include <hwui/Typeface.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/LocaleList.h>
-#include <minikin/Measurement.h>
-#include <minikin/MinikinPaint.h>
-#include <unicode/utf16.h>
+namespace android {
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <vector>
+namespace {
-namespace android {
+void copyMinikinRectToSkRect(const minikin::MinikinRect& minikinRect, SkRect* skRect) {
+ skRect->fLeft = minikinRect.mLeft;
+ skRect->fTop = minikinRect.mTop;
+ skRect->fRight = minikinRect.mRight;
+ skRect->fBottom = minikinRect.mBottom;
+}
+
+} // namespace
static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
const SkPoint pos[], SkPath* dst) {
@@ -105,8 +112,8 @@ namespace PaintGlue {
float measured = 0;
std::unique_ptr<float[]> advancesArray(new float[count]);
- MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
- 0, count, count, advancesArray.get());
+ MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
+ count, count, advancesArray.get(), nullptr);
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -196,9 +203,9 @@ namespace PaintGlue {
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- const float advance = MinikinUtils::measureText(paint,
- static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
- advancesArray.get());
+ const float advance = MinikinUtils::measureText(
+ paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
+ contextCount, advancesArray.get(), nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -236,7 +243,7 @@ namespace PaintGlue {
minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
- advancesArray.get());
+ advancesArray.get(), nullptr);
size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
start, count, offset, moveOpt);
return static_cast<jint>(result);
@@ -500,7 +507,7 @@ namespace PaintGlue {
static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
const jchar buf[], jint start, jint count, jint bufSize,
jboolean isRtl, jint offset, jfloatArray advances,
- jint advancesIndex) {
+ jint advancesIndex, SkRect* drawBounds) {
if (advances) {
size_t advancesLength = env->GetArrayLength(advances);
if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -509,14 +516,23 @@ namespace PaintGlue {
}
}
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ minikin::MinikinRect bounds;
if (offset == start + count && advances == nullptr) {
- return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
- bufSize, nullptr);
+ float result =
+ MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+ bufSize, nullptr, drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
+ return result;
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
if (advances) {
minikin::distributeAdvances(advancesArray.get(), buf, start, count);
@@ -532,7 +548,7 @@ namespace PaintGlue {
ScopedCharArrayRO textArray(env, text);
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, nullptr, 0);
+ isRtl, offset - contextStart, nullptr, 0, nullptr);
return result;
}
@@ -540,13 +556,19 @@ namespace PaintGlue {
jcharArray text, jint start, jint end,
jint contextStart, jint contextEnd,
jboolean isRtl, jint offset,
- jfloatArray advances, jint advancesIndex) {
+ jfloatArray advances, jint advancesIndex,
+ jobject drawBounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
+ SkRect skDrawBounds;
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, advances, advancesIndex);
+ isRtl, offset - contextStart, advances, advancesIndex,
+ drawBounds ? &skDrawBounds : nullptr);
+ if (drawBounds != nullptr) {
+ GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
+ }
return result;
}
@@ -555,7 +577,7 @@ namespace PaintGlue {
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), nullptr);
return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
}
@@ -571,7 +593,7 @@ namespace PaintGlue {
return result;
}
- static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+ static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
const int kElegantAscent = 1900;
@@ -584,7 +606,7 @@ namespace PaintGlue {
minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = font->getSkewX();
bool savefakeBold = font->isEmbolden();
- MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+ MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
SkScalar spacing = font->getMetrics(metrics);
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
@@ -600,6 +622,17 @@ namespace PaintGlue {
metrics->fLeading = size * kElegantLeading / 2048;
spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
}
+
+ if (useLocale) {
+ minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+ minikin::MinikinExtent extent =
+ typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+ metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+ metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+ metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+ metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+ }
+
return spacing;
}
@@ -612,7 +645,7 @@ namespace PaintGlue {
MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
metrics.fAscent = extent.ascent;
metrics.fDescent = extent.descent;
@@ -664,20 +697,21 @@ namespace PaintGlue {
}
}
- static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+ SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
GraphicsJNI::set_metrics(env, metricsObj, metrics);
return SkScalarToFloat(spacing);
}
- static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, useLocale);
return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
}
-
// ------------------ @CriticalNative ---------------------------
static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -821,9 +855,11 @@ namespace PaintGlue {
static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
Paint* obj = reinterpret_cast<Paint *>(objHandle);
- SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle);
- obj->setColorFilter(sk_ref_sp(filter));
- return reinterpret_cast<jlong>(obj->getColorFilter());
+ auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
+ obj->setColorFilter(skColorFilter);
+ return filterHandle;
}
static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) {
@@ -899,15 +935,39 @@ namespace PaintGlue {
obj->setMinikinLocaleListId(minikinLocaleListId);
}
- static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+ // Note: Following three values must be equal to the ones in Java file: Paint.java.
+ constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+ constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+ constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+ static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+ const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+ return ELEGANT_TEXT_HEIGHT_ENABLED;
+ } else {
+ return ELEGANT_TEXT_HEIGHT_DISABLED;
+ }
+ } else {
+ return ELEGANT_TEXT_HEIGHT_UNSET;
+ }
}
- static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+ static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- obj->setFamilyVariant(
- aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+ switch (value) {
+ case ELEGANT_TEXT_HEIGHT_ENABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_DISABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_UNSET:
+ default:
+ obj->resetFamilyVariant();
+ return;
+ }
}
static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -978,19 +1038,19 @@ namespace PaintGlue {
static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fAscent);
}
static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fDescent);
}
static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar position;
if (metrics.hasUnderlinePosition(&position)) {
return SkScalarToFloat(position);
@@ -1002,7 +1062,7 @@ namespace PaintGlue {
static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar thickness;
if (metrics.hasUnderlineThickness(&thickness)) {
return SkScalarToFloat(thickness);
@@ -1083,7 +1143,7 @@ static const JNINativeMethod methods[] = {
(void*)PaintGlue::getCharArrayBounds},
{"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+ {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
(void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
{"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
@@ -1097,9 +1157,9 @@ static const JNINativeMethod methods[] = {
{"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
{"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
(void*)PaintGlue::setFontFeatureSettings},
- {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+ {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
(void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+ {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
(void*)PaintGlue::getFontMetricsInt},
// --------------- @CriticalNative ------------------
@@ -1142,8 +1202,8 @@ static const JNINativeMethod methods[] = {
{"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
{"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
(void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
- {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+ {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+ {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
{"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
{"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
{"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index f3db1705e694..dcd3fa4932fc 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
#include "Bitmap.h"
+#include "ColorFilter.h"
#include "GraphicsJNI.h"
#include "SkBlendMode.h"
#include "SkImageFilter.h"
#include "SkImageFilters.h"
#include "graphics_jni_helpers.h"
#include "utils/Blur.h"
-#include <utils/Log.h>
using namespace android::uirenderer;
@@ -76,11 +76,13 @@ static jlong createColorFilterEffect(
jlong colorFilterHandle,
jlong inputFilterHandle
) {
- auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle);
+ auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
- sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter(
- sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr);
- return reinterpret_cast<jlong>(colorFilterImageFilter.release());
+ sk_sp<SkImageFilter> colorFilterImageFilter =
+ SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr);
+ return reinterpret_cast<jlong>(colorFilterImageFilter.release());
}
static jlong createBlendModeEffect(
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be6f55b..a952be020855 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
#include <vector>
#include "Gainmap.h"
@@ -68,21 +65,41 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
}
-static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool isDirectSampled,
- const SkSamplingOptions& sampling) {
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkGainmapInfo sNoOpGainmap = {
+ .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
+ .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
+ .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
+ .fDisplayRatioSdr = 1.f,
+ .fDisplayRatioHdr = 1.f,
+};
+
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+ jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
+ bool isDirectSampled, jlong overrideGainmapPtr) {
+ SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
+ : SkSamplingOptions(filter ? SkFilterMode::kLinear
+ : SkFilterMode::kNearest,
+ SkMipmapMode::kNone);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
sk_sp<SkImage> image;
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
image = bitmap.makeImage();
+ if (!gainmap && bitmap.hasGainmap()) {
+ gainmap = bitmap.gainmap().get();
+ }
- if (!isDirectSampled && bitmap.hasGainmap()) {
- sk_sp<SkShader> gainmapShader = MakeGainmapShader(
- image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
- (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
+ sk_sp<SkShader> gainmapShader =
+ MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
if (gainmapShader) {
if (matrix) {
gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
@@ -114,26 +131,6 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j
///////////////////////////////////////////////////////////////////////////////////////////////
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool filter,
- bool isDirectSampled) {
- SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
- SkMipmapMode::kNone);
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
- jlong bitmapHandle, jint tileModeX,
- jint tileModeY, jint maxAniso,
- bool isDirectSampled) {
- auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
const size_t count = env->GetArrayLength(colorArray);
const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -422,8 +419,7 @@ static const JNINativeMethod gShaderMethods[] = {
};
static const JNINativeMethod gBitmapShaderMethods[] = {
- {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
- {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+ {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
};
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 69418b09fee6..4dbfa88d6301 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkStream.h"
#include "YuvToJpegEncoder.h"
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index f060bb32031a..426644ee6a4e 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -84,7 +84,7 @@ static void android_view_DisplayListCanvas_resetDisplayListCanvas(CRITICAL_JNI_P
canvas->resetRecording(width, height, renderNode);
}
-static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) {
+static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
#else
@@ -175,14 +175,14 @@ static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAM
const char* const kClassPathName = "android/graphics/RecordingCanvas";
static JNINativeMethod gMethods[] = {
+ {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+ {"nGetMaximumTextureHeight", "()I",
+ (void*)android_view_DisplayListCanvas_getMaxTextureSize},
// ------------ @CriticalNative --------------
{"nCreateDisplayListCanvas", "(JII)J",
(void*)android_view_DisplayListCanvas_createDisplayListCanvas},
{"nResetDisplayListCanvas", "(JJII)V",
(void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
- {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
- {"nGetMaximumTextureHeight", "()I",
- (void*)android_view_DisplayListCanvas_getMaxTextureSize},
{"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
{"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
{"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c3be80..e3cdee6e7034 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d04de37f6961..422ffeaecfd0 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <FrameInfo.h>
@@ -27,7 +25,7 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
@@ -35,6 +33,7 @@
#include <SkTypeface.h>
#include <dlfcn.h>
#include <gui/TraceUtils.h>
+#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
@@ -54,6 +53,7 @@
#include <algorithm>
#include <atomic>
+#include <log/log.h>
#include <vector>
#include "JvmErrorReporter.h"
@@ -477,7 +477,7 @@ public:
// actually cross thread boundaries here, make a copy so it's immutable proper
if (bitmap && !bitmap->isImmutable()) {
ATRACE_NAME("Copying mutable bitmap");
- return SkImage::MakeFromBitmap(*bitmap);
+ return SkImages::RasterFromBitmap(*bitmap);
}
if (img->isTextureBacked()) {
ATRACE_NAME("Readback of texture image");
@@ -497,7 +497,7 @@ public:
return sk_ref_sp(img);
}
bm.setImmutable();
- return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+ return SkImages::PinnableRasterFromBitmap(bm);
}
return sk_ref_sp(img);
}
@@ -524,7 +524,16 @@ public:
if (iter != context->mTextureMap.end()) {
img = iter->second.get();
}
- return img->encodeToData();
+ if (!img) {
+ return nullptr;
+ }
+ // The following encode (specifically the pixel readback) will fail on a
+ // texture-backed image. They should already be raster images, but on
+ // the off-chance they aren't, we will just serialize it as nothing.
+ if (img->isTextureBacked()) {
+ return SkData::MakeEmpty();
+ }
+ return SkPngEncoder::Encode(nullptr, img, {});
}
void serialize(SkWStream* stream) const override {
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8c7b9a4b5e94..2a218a25913d 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -243,6 +243,13 @@ static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMM
return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC);
}
+static jboolean android_view_RenderNode_setBackdropRenderEffect(
+ CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong renderEffectPtr) {
+ SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr);
+ return SET_AND_DIRTY(mutateLayerProperties().setBackdropImageFilter, imageFilter,
+ RenderNode::GENERIC);
+}
+
static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
bool hasOverlappingRendering) {
return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering,
@@ -792,6 +799,8 @@ static const JNINativeMethod gMethods[] = {
{"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha},
{"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect},
+ {"nSetBackdropRenderEffect", "(JJ)Z",
+ (void*)android_view_RenderNode_setBackdropRenderEffect},
{"nSetHasOverlappingRendering", "(JZ)Z",
(void*)android_view_RenderNode_setHasOverlappingRendering},
{"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint},
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9a04be..b86c74fe0e47 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Interpolator.h>
#include <cutils/log.h>
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f853c1d..40be9243affb 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Animator.h>
#include <Interpolator.h>
#include <RenderProperties.h>
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb308c8..ade48f26937f 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include <hwui/Paint.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "PathParser.h"
#include "VectorDrawable.h"
-#include <hwui/Paint.h>
-
namespace android {
using namespace uirenderer;
using namespace uirenderer::VectorDrawable;
@@ -108,8 +108,9 @@ static jint draw(JNIEnv* env, jobject, jlong treePtr, jlong canvasPtr,
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
SkRect rect;
GraphicsJNI::jrect_to_rect(env, jrect, &rect);
- SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr);
- return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache);
+ auto colorFilter = ColorFilter::fromJava(colorFilterPtr);
+ auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr;
+ return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache);
}
/**
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 1af60b2f5fae..2ec94c954fe9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "Font.h"
#include "SkData.h"
#include "SkFont.h"
@@ -127,7 +124,7 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo
static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
jint weight, jboolean italic, jint ttcIndex) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
// Reconstruct SkTypeface with different arguments from existing SkTypeface.
@@ -159,7 +156,7 @@ static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong
static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
jlong paintHandle, jobject rect) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -179,7 +176,7 @@ static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint g
static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
jobject metricsObj) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -209,7 +206,7 @@ static jlong Font_cloneFont(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
// Fast Native
static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
minikinFont->GetFontSize());
}
@@ -217,7 +214,7 @@ static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
// Critical Native
static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return reinterpret_cast<jlong>(font->font->typeface()->GetFontData());
+ return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
}
// Critical Native
@@ -236,7 +233,7 @@ static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) {
}
return env->NewStringUTF(path.c_str());
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
const std::string& path = minikinFont->GetFontPath();
if (path.empty()) {
return nullptr;
@@ -275,7 +272,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
reader.skipString(); // fontPath
return reader.read<int>();
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetFontIndex();
}
}
@@ -289,7 +286,7 @@ static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
reader.skip<int>(); // fontIndex
return reader.readArray<minikin::FontVariation>().second;
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetAxes().size();
}
}
@@ -304,7 +301,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde
reader.skip<int>(); // fontIndex
var = reader.readArray<minikin::FontVariation>().first[index];
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
var = minikinFont->GetAxes().at(index);
}
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
@@ -314,7 +311,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde
// Critical Native
static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return font->font->typeface()->GetSourceId();
+ return font->font->baseTypeface()->GetSourceId();
}
static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index ee158ee4e316..462c8c8f2fb0 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedUtfChars.h>
@@ -60,7 +57,7 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
jstring langTags, jint variant, jboolean isCustomFallback,
- jboolean isDefaultFallback) {
+ jboolean isDefaultFallback, jint variationFamilyType) {
std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -71,7 +68,8 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
- isCustomFallback, isDefaultFallback);
+ isCustomFallback, isDefaultFallback,
+ static_cast<minikin::VariationFamilyType>(variationFamilyType));
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -121,7 +119,7 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
{"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
{"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
- {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build},
{"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa1bd83..3b18f5f54187 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
#include <sys/types.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d202828b85..6887fdacd68f 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
#include "PdfUtils.h"
-#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
#include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
namespace android {
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd9f7b1..322af7e9f3ee 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
#include <minikin/GraphemeBreak.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 69865171a09d..c512256ed9b9 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
#include "utils/misc.h"
#include "utils/Log.h"
#include "graphics_jni_helpers.h"
@@ -54,13 +51,12 @@ static inline minikin::android::StaticLayoutNative* toNative(jlong ptr) {
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
// hyphenFrequency)
-static jlong nInit(JNIEnv* env, jclass /* unused */,
- jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) {
+static jlong nInit(JNIEnv* env, jclass /* unused */, jint breakStrategy, jint hyphenationFrequency,
+ jboolean isJustified, jintArray indents, jboolean useBoundsForWidth) {
return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
static_cast<minikin::BreakStrategy>(breakStrategy),
- static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
- isJustified,
- jintArrayToFloatVector(env, indents)));
+ static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), isJustified,
+ jintArrayToFloatVector(env, indents), useBoundsForWidth));
}
static void nFinish(jlong nativePtr) {
@@ -131,39 +127,44 @@ static jlong nGetReleaseResultFunc(CRITICAL_JNI_PARAMS) {
}
static const JNINativeMethod gMethods[] = {
- // Fast Natives
- {"nInit", "("
- "I" // breakStrategy
- "I" // hyphenationFrequency
- "Z" // isJustified
- "[I" // indents
- ")J", (void*) nInit},
-
- // Critical Natives
- {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},
-
- // Regular JNI
- {"nComputeLineBreaks", "("
- "J" // nativePtr
- "[C" // text
- "J" // MeasuredParagraph ptr.
- "I" // length
- "F" // firstWidth
- "I" // firstWidthLineCount
- "F" // restWidth
- "[F" // variableTabStops
- "F" // defaultTabStop
- "I" // indentsOffset
- ")J", (void*) nComputeLineBreaks},
-
- // Result accessors, CriticalNatives
- {"nGetLineCount", "(J)I", (void*)nGetLineCount},
- {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
- {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
- {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
- {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
- {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
- {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
+ // Fast Natives
+ {"nInit",
+ "("
+ "I" // breakStrategy
+ "I" // hyphenationFrequency
+ "Z" // isJustified
+ "[I" // indents
+ "Z" // useBoundsForWidth
+ ")J",
+ (void*)nInit},
+
+ // Critical Natives
+ {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc},
+
+ // Regular JNI
+ {"nComputeLineBreaks",
+ "("
+ "J" // nativePtr
+ "[C" // text
+ "J" // MeasuredParagraph ptr.
+ "I" // length
+ "F" // firstWidth
+ "I" // firstWidthLineCount
+ "F" // restWidth
+ "[F" // variableTabStops
+ "F" // defaultTabStop
+ "I" // indentsOffset
+ ")J",
+ (void*)nComputeLineBreaks},
+
+ // Result accessors, CriticalNatives
+ {"nGetLineCount", "(J)I", (void*)nGetLineCount},
+ {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
+ {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
+ {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
+ {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
+ {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
+ {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
};
int register_android_graphics_text_LineBreaker(JNIEnv* env) {
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800651ef..746745afbf09 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
#include "GraphicsJNI.h"
#include "utils/misc.h"
#include "utils/Log.h"
@@ -65,13 +62,14 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) {
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
- jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+ jint start, jint end, jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
toBuilder(builderPtr)
- ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+ isRtl);
}
// Regular JNI
@@ -84,13 +82,14 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong
// Regular JNI
static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
jcharArray javaText, jboolean computeHyphenation,
- jboolean computeLayout, jboolean fastHyphenationMode) {
+ jboolean computeLayout, jboolean computeBounds,
+ jboolean fastHyphenationMode) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
return toJLong(toBuilder(builderPtr)
- ->build(textBuffer, computeHyphenation, computeLayout,
+ ->build(textBuffer, computeHyphenation, computeLayout, computeBounds,
fastHyphenationMode, toMeasuredParagraph(hintPtr))
.release());
}
@@ -161,9 +160,9 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
- {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+ {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
- {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+ {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
};
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8e4dd53069f4..6c05346d26da 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
@@ -62,7 +59,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
const minikin::Font* font = layout.getFont(i);
if (seenFonts.find(font) != seenFonts.end()) continue;
minikin::MinikinExtent extent = {};
- font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+ layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
overallAscent = std::min(overallAscent, extent.ascent);
overallDescent = std::max(overallDescent, extent.descent);
}
@@ -148,6 +145,30 @@ static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i
}
// CriticalNative
+static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeBold();
+}
+
+// CriticalNative
+static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeItalic();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).wghtAdjustment();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).italAdjustment();
+}
+
+// CriticalNative
static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i);
@@ -185,15 +206,19 @@ static const JNINativeMethod gMethods[] = {
};
static const JNINativeMethod gResultMethods[] = {
- { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
- { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
- { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
- { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
- { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
- { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
- { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
- { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
- { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+ {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount},
+ {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance},
+ {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent},
+ {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent},
+ {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId},
+ {"nGetX", "(JI)F", (void*)TextShaper_Result_getX},
+ {"nGetY", "(JI)F", (void*)TextShaper_Result_getY},
+ {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont},
+ {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold},
+ {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic},
+ {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
+ {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
+ {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
new file mode 100644
index 000000000000..e81cbfb508ae
--- /dev/null
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BackdropFilterDrawable.h"
+
+#include <SkImage.h>
+#include <SkSurface.h>
+
+#include "RenderNode.h"
+#include "RenderNodeDrawable.h"
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+BackdropFilterDrawable::~BackdropFilterDrawable() {}
+
+bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties,
+ int backdropImageWidth, int backdropImageHeight) {
+ // the drawing bounds for blurred content.
+ mDstBounds.setWH(properties.getWidth(), properties.getHeight());
+
+ float alphaMultiplier = 1.0f;
+ RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
+
+ // get proper subset for previous content.
+ canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds);
+ SkRect imageSubset(mImageSubset);
+ // ensure the subset is inside bounds of previous content.
+ if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) {
+ return false;
+ }
+
+ // correct the drawing bounds if subset was changed.
+ if (mImageSubset != imageSubset) {
+ SkMatrix inverse;
+ if (canvas->getTotalMatrix().invert(&inverse)) {
+ inverse.mapRect(&mDstBounds, mImageSubset);
+ }
+ }
+
+ // follow the alpha from the target RenderNode.
+ mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
+ return true;
+}
+
+void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
+ const RenderProperties& properties = mTargetRenderNode->properties();
+ auto* backdropFilter = properties.layerProperties().getBackdropImageFilter();
+ auto* surface = canvas->getSurface();
+ if (!backdropFilter || !surface) {
+ return;
+ }
+
+ auto backdropImage = surface->makeImageSnapshot();
+ // sync necessary properties from target RenderNode.
+ if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
+ return;
+ }
+
+ auto imageSubset = mImageSubset.roundOut();
+#ifdef __ANDROID__
+ if (canvas->recordingContext()) {
+ backdropImage =
+ SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
+ imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+ } else
+#endif
+ {
+ backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
+ imageSubset, &mOutSubset, &mOutOffset);
+ }
+ canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
+ SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
+ SkCanvas::kStrict_SrcRectConstraint);
+}
+
+} // namespace skiapipeline
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
new file mode 100644
index 000000000000..9e35837675ae
--- /dev/null
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <SkPaint.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+class RenderProperties;
+
+namespace skiapipeline {
+
+/**
+ * This drawable captures it's backdrop content and render it with a
+ * image filter.
+ */
+class BackdropFilterDrawable : public SkDrawable {
+public:
+ BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas)
+ : mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {}
+
+ ~BackdropFilterDrawable();
+
+private:
+ RenderNode* mTargetRenderNode;
+ SkPaint mPaint;
+
+ SkRect mDstBounds;
+ SkRect mImageSubset;
+ SkIRect mOutSubset;
+ SkIPoint mOutOffset;
+
+ /**
+ * Check all necessary properties before actual drawing.
+ * Return true if ready to draw.
+ */
+ bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth,
+ int backdropImageHeight);
+
+protected:
+ void onDraw(SkCanvas* canvas) override;
+
+ virtual SkRect onGetBounds() override { return mBounds; }
+ const SkRect mBounds;
+};
+
+} // namespace skiapipeline
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 8d5967bbd461..5d3fb30769ed 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -21,10 +21,15 @@
#include "GrBackendSurface.h"
#include "RenderNode.h"
#include "SkAndroidFrameworkUtils.h"
+#include "SkCanvas.h"
+#include "SkCanvasAndroid.h"
#include "SkClipStack.h"
#include "SkRect.h"
#include "SkM44.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include "utils/GLUtils.h"
#include <effects/GainmapRenderer.h>
#include "renderthread/CanvasContext.h"
@@ -34,7 +39,7 @@ namespace uirenderer {
namespace skiapipeline {
static void setScissor(int viewportHeight, const SkIRect& clip) {
- SkASSERT(!clip.isEmpty());
+ LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip");
// transform to Y-flipped GL space, and prevent negatives
GLint y = viewportHeight - clip.fBottom;
GLint height = (viewportHeight - clip.fTop) - y;
@@ -42,9 +47,9 @@ static void setScissor(int viewportHeight, const SkIRect& clip) {
}
static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
- GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget();
+ GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
GrGLFramebufferInfo fboInfo;
- LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+ LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
"getGLFrameBufferInfo failed");
*outFboID = fboInfo.fFBOID;
@@ -76,13 +81,13 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
}
// flush will create a GrRenderTarget if not already present.
- canvas->flush();
+ directContext->flushAndSubmit();
GLuint fboID = 0;
SkISize fboSize;
GetFboDetails(canvas, &fboID, &fboSize);
- SkIRect surfaceBounds = canvas->topLayerBounds();
+ SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas);
SkIRect clipBounds = canvas->getDeviceClipBounds();
SkM44 mat4(canvas->getLocalToDevice());
SkRegion clipRegion;
@@ -95,12 +100,14 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
SkImageInfo surfaceInfo =
canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
tmpSurface =
- SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
+ SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
GrGLFramebufferInfo fboInfo;
- if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
- .getGLFramebufferInfo(&fboInfo)) {
+ if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+ SkSurfaces::GetBackendRenderTarget(
+ tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+ &fboInfo)) {
ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
return;
}
@@ -163,7 +170,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
// GL ops get inserted here if previous flush is missing, which could dirty the stencil
bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
- tmpCanvas->flush(); // need this flush for the single op that draws into the stencil
+ directContext->flushAndSubmit(); // need this flush for the single op that draws into the stencil
// ensure that the framebuffer that the webview will render into is bound before after we
// draw into the stencil
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index da4f66d45a70..2b2e3995d17e 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -15,21 +15,25 @@
*/
#include "RenderNodeDrawable.h"
+
#include <SkPaint.h>
#include <SkPaintFilterCanvas.h>
#include <SkPoint.h>
#include <SkRRect.h>
#include <SkRect.h>
#include <gui/TraceUtils.h>
+#include <include/effects/SkImageFilters.h>
+#ifdef __ANDROID__
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#endif
+
+#include <optional>
+
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "StretchMask.h"
#include "TransformCanvas.h"
-#include <include/effects/SkImageFilters.h>
-
-#include <optional>
-
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -255,9 +259,19 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
if (imageFilter) {
auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
- snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
- subset, clipBounds.roundOut(),
- &srcBounds, &offset);
+
+#ifdef __ANDROID__
+ if (recordingContext) {
+ snapshotImage = SkImages::MakeWithFilter(
+ recordingContext, snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds, &offset);
+ } else
+#endif
+ {
+ snapshotImage = SkImages::MakeWithFilter(snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds,
+ &offset);
+ }
}
} else {
const auto snapshotResult = renderNode->updateSnapshotIfRequired(
@@ -362,7 +376,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
}
void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
- float* alphaMultiplier) {
+ float* alphaMultiplier, bool ignoreLayer) {
if (properties.getLeft() != 0 || properties.getTop() != 0) {
canvas->translate(properties.getLeft(), properties.getTop());
}
@@ -378,7 +392,8 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S
canvas->concat(*properties.getTransformMatrix());
}
}
- if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
+ if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale &&
+ !ignoreLayer) {
const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
if (!stretch.isEmpty()) {
canvas->concat(
@@ -388,10 +403,10 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S
const bool isLayer = properties.effectiveLayerType() != LayerType::None;
int clipFlags = properties.getClippingFlags();
if (properties.getAlpha() < 1) {
- if (isLayer) {
+ if (isLayer && !ignoreLayer) {
clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
}
- if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+ if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering()) || ignoreLayer) {
*alphaMultiplier = properties.getAlpha();
} else {
// savelayer needed to create an offscreen buffer
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index c7582e734009..818ac45bf346 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -120,7 +120,7 @@ private:
* Applies the rendering properties of a view onto a SkCanvas.
*/
static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
- float* alphaMultiplier);
+ float* alphaMultiplier, bool ignoreLayer = false);
/**
* Stores transform on the canvas at time of recording and is used for
@@ -149,6 +149,11 @@ private:
* display list that is searched for any render nodes with getProjectBackwards==true
*/
SkiaDisplayList* mProjectedDisplayList = nullptr;
+
+ /**
+ * Allow BackdropFilterDrawable to apply same render properties onto SkCanvas.
+ */
+ friend class BackdropFilterDrawable;
};
} // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 11977bd54c2c..136740c799dd 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -27,7 +27,6 @@
#include <SkRect.h>
#include <SkScalar.h>
#include <SkShadowUtils.h>
-#include <include/private/SkShadowFlags.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 74955503dbb1..6ccb212fe6ca 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -26,6 +26,7 @@
#include <string>
#include <vector>
+class GrDirectContext;
class SkData;
namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index af2d3b34bac7..5c8285a8e1e9 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -66,6 +66,12 @@ void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn)
}
}
+void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const {
+ for (auto& child : mChildNodes) {
+ child.getRenderNode()->visit(func);
+ }
+}
+
static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
Vector3 {bounds.fRight, bounds.fTop, 0},
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 7af31a4dc4c6..e5bd5c9b2a3b 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -145,6 +145,8 @@ public:
*/
void updateChildren(std::function<void(RenderNode*)> updateFn);
+ void visit(std::function<void(const RenderNode&)> func) const;
+
/**
* Returns true if there is a child render node that is a projection receiver.
*/
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 10427039c35a..814b682a2b98 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@ SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
, mTotalSize("bytes", 0)
, mPurgeableSize("bytes", 0) {}
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
for (auto& resource : mResourceMap) {
- if (SkStrContains(resourceName, resource.first)) {
+ if (resourceName.find(resource.first) != std::string::npos) {
return resource.second;
}
}
- return nullptr;
+ return std::nullopt;
}
void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@ void SkiaMemoryTracer::processElement() {
}
// find the type if one exists
- const char* type;
+ std::string type;
auto typeResult = mCurrentValues.find("type");
if (typeResult != mCurrentValues.end()) {
type = typeResult->second.units;
@@ -71,14 +71,13 @@ void SkiaMemoryTracer::processElement() {
}
// compute the type if we are itemizing or use the default "size" if we are not
- const char* key = (mItemizeType) ? type : sizeResult->first;
- SkASSERT(key != nullptr);
+ std::string key = (mItemizeType) ? type : sizeResult->first;
// compute the top level element name using either the map or category key
- const char* resourceName = mapName(mCurrentElement.c_str());
- if (mCategoryKey != nullptr) {
+ std::optional<std::string> resourceName = mapName(mCurrentElement);
+ if (mCategoryKey) {
// find the category if one exists
- auto categoryResult = mCurrentValues.find(mCategoryKey);
+ auto categoryResult = mCurrentValues.find(*mCategoryKey);
if (categoryResult != mCurrentValues.end()) {
resourceName = categoryResult->second.units;
} else if (mItemizeType) {
@@ -87,11 +86,11 @@ void SkiaMemoryTracer::processElement() {
}
// if we don't have a pretty name then use the dumpName
- if (resourceName == nullptr) {
- resourceName = mCurrentElement.c_str();
+ if (!resourceName) {
+ resourceName = mCurrentElement;
}
- auto result = mResults.find(resourceName);
+ auto result = mResults.find(*resourceName);
if (result != mResults.end()) {
auto& resourceValues = result->second;
typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@ void SkiaMemoryTracer::processElement() {
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@ void SkiaMemoryTracer::logOutput(String8& log) {
for (const auto& typedValue : namedItem.second) {
TraceValue traceValue = convertUnits(typedValue.second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
- log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
- traceValue.units, traceValue.count, entry);
+ log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
} else {
auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@ void SkiaMemoryTracer::logOutput(String8& log) {
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
}
}
@@ -156,7 +157,7 @@ void SkiaMemoryTracer::logOutput(String8& log) {
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@ void SkiaMemoryTracer::logTotals(String8& log) {
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if ("bytes" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if ("KB" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b0422c6f..dbfc86bc033c 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
#pragma once
-#include <SkString.h>
#include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
#include <utils/String8.h>
#include <unordered_map>
#include <vector>
@@ -60,17 +61,17 @@ private:
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ std::string units;
float value;
int count;
};
- const char* mapName(const char* resourceName);
+ std::optional<std::string> mapName(const std::string& resourceName);
void processElement();
TraceValue convertUnits(const TraceValue& value);
const std::vector<ResourcePair> mResourceMap;
- const char* mCategoryKey = nullptr;
+ std::optional<std::string> mCategoryKey;
const bool mItemizeType;
// variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@ private:
// variables storing information on the current node being dumped
std::string mCurrentElement;
- std::unordered_map<const char*, TraceValue> mCurrentValues;
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
// variable that stores the final format of the data after the individual elements are processed
- std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+ std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
-} /* namespace android */ \ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cf31173d266e..774478669058 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,9 @@
#include "SkiaOpenGLPipeline.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
@@ -138,7 +141,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
LOG_ALWAYS_FATAL("Unsupported color type.");
}
- GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
+ auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
+ STENCIL_BUFFER_SIZE, fboInfo);
SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
kUnknown_SkPixelGeometry);
@@ -150,9 +154,9 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
surface = getBufferSkSurface(bufferParams);
preTransform = bufferParams.getTransform();
} else {
- surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
- getSurfaceOrigin(), colorType,
- mSurfaceColorSpace, &props);
+ surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+ getSurfaceOrigin(), colorType,
+ mSurfaceColorSpace, &props);
preTransform = SkMatrix::I();
}
@@ -174,7 +178,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
{
ATRACE_NAME("flush commands");
- surface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(surface);
}
layerUpdateQueue->clear();
@@ -245,8 +249,7 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
- const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
- ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
+ mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
return true;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index cb23bcc166c8..e0f1f6ef44be 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,14 +16,16 @@
#include "SkiaPipeline.h"
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImageEncoder.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
-#include <SkImagePriv.h>
#include <SkMatrix.h>
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
@@ -75,7 +77,7 @@ bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
return false;
}
for (SkImage* image : mutableImages) {
- if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
mPinnedImages.emplace_back(sk_ref_sp(image));
} else {
return false;
@@ -86,7 +88,7 @@ bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
void SkiaPipeline::unpinImages() {
for (auto& image : mPinnedImages) {
- SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext());
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
}
mPinnedImages.clear();
}
@@ -187,9 +189,9 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator
kPremul_SkAlphaType, getSurfaceColorSpace());
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
// position
@@ -222,8 +224,8 @@ void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
auto image = bitmap->makeImage();
if (image.get()) {
- SkImage_pinAsTexture(image.get(), context);
- SkImage_unpinAsTexture(image.get(), context);
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
// A submit is necessary as there may not be a frame coming soon, so without a call
// to submit these texture uploads can just sit in the queue building up until
// we run out of RAM
@@ -439,6 +441,13 @@ void SkiaPipeline::endCapture(SkSurface* surface) {
procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
};
+ procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+ GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+ return SkPngEncoder::Encode(dCtx,
+ img,
+ SkPngEncoder::Options{});
+ };
+ procs.fImageCtx = mRenderThread.getGrContext();
auto data = picture->serialize(&procs);
savePictureAsync(data, mCapturedFile);
mCaptureSequence = 0;
@@ -621,7 +630,7 @@ sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
auto bufferColorSpace = bufferParams.getColorSpace();
if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
!SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
bufferColorSpace, nullptr, true);
mBufferColorSpace = bufferColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 3ca7eeb37a89..e917f9a66917 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -37,6 +37,7 @@
#include "NinePatchUtils.h"
#include "RenderNode.h"
#include "pipeline/skia/AnimatedDrawables.h"
+#include "pipeline/skia/BackdropFilterDrawable.h"
#ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc.
#include "pipeline/skia/GLFunctorDrawable.h"
#include "pipeline/skia/VkFunctorDrawable.h"
@@ -168,6 +169,14 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
// Put Vulkan WebViews with non-rectangular clips in a HW layer
renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex());
}
+
+ // draw backdrop filter drawable if needed.
+ if (renderNode->stagingProperties().layerProperties().getBackdropImageFilter()) {
+ auto* backdropFilterDrawable =
+ mDisplayList->allocateDrawable<BackdropFilterDrawable>(renderNode, asSkCanvas());
+ drawDrawable(backdropFilterDrawable);
+ }
+
drawDrawable(&renderNodeDrawable);
// use staging property, since recording on UI thread
@@ -227,6 +236,17 @@ void SkiaRecordingCanvas::handleMutableImages(Bitmap& bitmap, DrawImagePayload&
}
}
+void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) {
+ INHERITED::onFilterPaint(paint);
+ SkShader* shader = paint.getShader();
+ // TODO(b/264559422): This only works for very specifically a BitmapShader.
+ // It's better than nothing, though
+ SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+ if (image) {
+ mDisplayList->mMutableImages.push_back(image);
+ }
+}
+
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto payload = DrawImagePayload(bitmap);
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index a8e4580dc200..3bd091df1ece 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -105,6 +105,8 @@ private:
void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
+ void onFilterPaint(Paint& paint) override;
+
using INHERITED = SkiaCanvas;
};
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index cad3703d8d2b..1676787ef671 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -18,14 +18,13 @@
#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkSurface.h"
-#include "include/gpu/GpuTypes.h" // from Skia
#include "TransformCanvas.h"
#include "SkiaDisplayList.h"
using android::uirenderer::StretchMask;
-void StretchMask::draw(GrRecordingContext* context,
+void StretchMask::draw(GrRecordingContext*,
const StretchEffect& stretch,
const SkRect& bounds,
skiapipeline::SkiaDisplayList* displayList,
@@ -35,16 +34,14 @@ void StretchMask::draw(GrRecordingContext* context,
if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
mMaskSurface->height() != height) {
// Create a new surface if we don't have one or our existing size does
- // not match.
- mMaskSurface = SkSurface::MakeRenderTarget(
- context,
- skgpu::Budgeted::kYes,
- SkImageInfo::Make(
- width,
- height,
- SkColorType::kAlpha_8_SkColorType,
- SkAlphaType::kPremul_SkAlphaType)
- );
+ // not match. SkCanvas::makeSurface returns a new surface that will
+ // be GPU-backed if canvas was also.
+ mMaskSurface = canvas->makeSurface(SkImageInfo::Make(
+ width,
+ height,
+ SkColorType::kAlpha_8_SkColorType,
+ SkAlphaType::kPremul_SkAlphaType
+ ));
mIsDirty = true;
}
@@ -53,7 +50,7 @@ void StretchMask::draw(GrRecordingContext* context,
// Make sure to apply target transformation to the mask canvas
// to ensure the replayed drawing commands generate the same result
auto previousMatrix = displayList->mParentMatrix;
- displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+ displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3();
maskCanvas->save();
maskCanvas->drawColor(0, SkBlendMode::kClear);
TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index adf3c06b8624..475b110604e4 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -35,6 +35,8 @@
#include "effects/GainmapRenderer.h"
#include <SkBlendMode.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
namespace android {
namespace uirenderer {
@@ -183,9 +185,9 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
// drawing into the offscreen surface, so we need to reset it here.
canvas->resetMatrix();
- auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
- canvas->imageInfo().refColorSpace(),
- kBottomLeft_GrSurfaceOrigin);
+ auto functorImage = SkImages::DeferredFromAHardwareBuffer(
+ mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(),
+ kBottomLeft_GrSurfaceOrigin);
canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
canvas->restore();
}
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f9304a5c8..ed3fabc61708 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@ namespace android {
namespace uirenderer {
/**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
*/
struct DrawGlInfo {
// Input: current clip rect
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index babce88b8e1e..30d461271c89 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -17,6 +17,7 @@
#include "CacheManager.h"
#include <GrContextOptions.h>
+#include <GrTypes.h>
#include <SkExecutor.h>
#include <SkGraphics.h>
#include <math.h>
@@ -110,13 +111,18 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void
contextOptions->fPersistentCache = &cache;
}
+static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) {
+ return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly :
+ GrPurgeResourceOptions::kAllResources;
+}
+
void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
// flush and submit all work to the gpu and wait for it to finish
- mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
switch (mode) {
case TrimLevel::BACKGROUND:
@@ -130,7 +136,7 @@ void CacheManager::trimMemory(TrimLevel mode) {
// that have persistent data to be purged in LRU order.
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
@@ -150,7 +156,7 @@ void CacheManager::trimCaches(CacheTrimLevel mode) {
case CacheTrimLevel::ALL_CACHES:
SkGraphics::PurgeAllCaches();
if (mGrContext) {
- mGrContext->purgeUnlockedResources(false);
+ mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
}
break;
default:
@@ -163,7 +169,8 @@ void CacheManager::trimStaleResources() {
return;
}
mGrContext->flushAndSubmit();
- mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+ mGrContext->performDeferredCleanup(std::chrono::seconds(30),
+ GrPurgeResourceOptions::kAllResources);
}
void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -277,14 +284,15 @@ void CacheManager::onThreadIdle() {
const nsecs_t now = systemTime(CLOCK_MONOTONIC);
// Rate limiting
- if ((now - mLastDeferredCleanup) < 25_ms) {
+ if ((now - mLastDeferredCleanup) > 25_ms) {
mLastDeferredCleanup = now;
const nsecs_t frameCompleteNanos = mFrameCompletions[0];
const nsecs_t frameDiffNanos = now - frameCompleteNanos;
const nsecs_t cleanupMillis =
- ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+ mMemoryPolicy.maximumResourceRetention));
mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
- mMemoryPolicy.purgeScratchOnly);
+ toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
}
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac209696..bcfa4f359d83 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@ public:
void unregisterCanvasContext(CanvasContext* context);
void onContextStopped(CanvasContext* context);
+ bool areAllContextsStopped();
+
private:
friend class RenderThread;
explicit CacheManager(RenderThread& thread);
void setupCacheLimits();
- bool areAllContextsStopped();
void checkUiHidden();
void scheduleDestroyContext();
void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f5b3ca602469..7fac0c9776ac 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,8 +123,9 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline))
- , mHintSessionWrapper(uiThreadId, renderThreadId) {
+ , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
mRenderThread.cacheManager().registerCanvasContext(this);
+ mRenderThread.renderState().registerContextCallback(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,8 @@ CanvasContext::~CanvasContext() {
}
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
+ mRenderThread.renderState().removeContextCallback(this);
+ mHintSessionWrapper->destroy();
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -160,6 +163,7 @@ void CanvasContext::destroy() {
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
+ mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
}
static void setBufferCount(ANativeWindow* window) {
@@ -356,8 +360,9 @@ bool CanvasContext::makeCurrent() {
return true;
}
-static bool wasSkipped(FrameInfo* info) {
- return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
+static std::optional<SkippedFrameReason> wasSkipped(FrameInfo* info) {
+ if (info) return info->getSkippedFrameReason();
+ return std::nullopt;
}
bool CanvasContext::isSwapChainStuffed() {
@@ -406,13 +411,26 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (wasSkipped(mCurrentFrameInfo)) {
+ if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
// Use the oldest skipped frame in case we skip more than a single frame
if (!mSkippedFrameInfo) {
- mSkippedFrameInfo.emplace();
- mSkippedFrameInfo->vsyncId =
- mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
- mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ switch (*reason) {
+ case SkippedFrameReason::AlreadyDrawn:
+ case SkippedFrameReason::NoBuffer:
+ case SkippedFrameReason::NoOutputTarget:
+ mSkippedFrameInfo.emplace();
+ mSkippedFrameInfo->vsyncId =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ mSkippedFrameInfo->startTime =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ break;
+ case SkippedFrameReason::DrawingOff:
+ case SkippedFrameReason::ContextIsStopped:
+ case SkippedFrameReason::NothingToDraw:
+ // Do not report those as skipped frames as there was no frame expected to be
+ // drawn
+ break;
+ }
}
} else {
mCurrentFrameInfo = mJankTracker.startFrame();
@@ -426,7 +444,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
info.damageAccumulator = &mDamageAccumulator;
info.layerUpdateQueue = &mLayerUpdateQueue;
info.damageGenerationId = mDamageId++;
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -446,8 +464,8 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
mIsDirty = true;
if (CC_UNLIKELY(!hasOutputTarget())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
return;
}
@@ -462,23 +480,23 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
if (vsyncDelta < 2_ms) {
// Already drew for this vsync pulse, UI draw request missed
// the deadline for RT animations
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
}
} else {
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
}
// TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
// be an allowable combination?
if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
}
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
int err = mNativeSurface->reserveNext();
if (err != OK) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err));
if (err != TIMED_OUT) {
// A timed out surface can still recover, but assume others are permanently dead.
@@ -487,11 +505,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
}
}
} else {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
}
bool postedFrameCallback = false;
- if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
+ if (info.out.hasAnimations || info.out.skippedFrameReason) {
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
}
@@ -544,7 +562,11 @@ Frame CanvasContext::getFrame() {
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
@@ -557,9 +579,20 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
mSyncDelayDuration = 0;
mIdleDuration = 0;
- if (!Properties::isDrawingEnabled() ||
- (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
+ if (!Properties::isDrawingEnabled()) {
+ return SkippedFrameReason::DrawingOff;
+ }
+
+ if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
+ return SkippedFrameReason::NothingToDraw;
+ }
+
+ return std::nullopt;
+ }();
+ if (skippedFrameReason) {
+ mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
+
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
// free its staging buffers.
@@ -739,7 +772,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
- mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+ mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -747,7 +780,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
- mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+ mHintSessionWrapper->reportActualWorkDuration(actualDuration);
}
mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -890,10 +923,10 @@ const SkM44& CanvasContext::getPixelSnapMatrix() const {
}
void CanvasContext::prepareAndDraw(RenderNode* node) {
- ATRACE_CALL();
+ int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+ ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
- int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
int64_t frameInterval = mRenderThread.timeLord().frameIntervalNanos();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -903,7 +936,7 @@ void CanvasContext::prepareAndDraw(RenderNode* node) {
TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
draw(info.out.solelyTextureViewUpdates);
} else {
// wait on fences so tasks don't overlap next frame
@@ -964,6 +997,10 @@ void CanvasContext::destroyHardwareResources() {
}
}
+void CanvasContext::onContextDestroyed() {
+ destroyHardwareResources();
+}
+
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
@@ -1081,11 +1118,11 @@ void CanvasContext::prepareSurfaceControlForWebview() {
}
void CanvasContext::sendLoadResetHint() {
- mHintSessionWrapper.sendLoadResetHint();
+ mHintSessionWrapper->sendLoadResetHint();
}
void CanvasContext::sendLoadIncreaseHint() {
- mHintSessionWrapper.sendLoadIncreaseHint();
+ mHintSessionWrapper->sendLoadIncreaseHint();
}
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1093,7 +1130,7 @@ void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
}
void CanvasContext::startHintSession() {
- mHintSessionWrapper.init();
+ mHintSessionWrapper->init();
}
bool CanvasContext::shouldDither() {
@@ -1102,6 +1139,12 @@ bool CanvasContext::shouldDither() {
return self->mColorMode != ColorMode::Default;
}
+void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const {
+ for (auto node : mRenderNodes) {
+ node->visit(func);
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 32ac5af94c14..37e4f7ecca54 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,6 +43,7 @@
#include "Lighting.h"
#include "ReliableSurface.h"
#include "RenderNode.h"
+#include "renderstate/RenderState.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
#include "utils/RingBuffer.h"
@@ -64,7 +65,7 @@ class Frame;
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
// TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +155,7 @@ public:
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
+ void onContextDestroyed() override;
DeferredLayerUpdater* createTextureLayer();
@@ -236,6 +238,8 @@ public:
static bool shouldDither();
+ void visitAllRenderNodes(std::function<void(const RenderNode&)>) const;
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
@@ -359,7 +363,7 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
- HintSessionWrapper mHintSessionWrapper;
+ std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
nsecs_t mLastDequeueBufferDuration = 0;
nsecs_t mSyncDelayDuration = 0;
nsecs_t mIdleDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 53b43ba417d0..1b333bfccbf1 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -104,7 +104,7 @@ void DrawFrameTask::run() {
info.forceDrawFrame = mForceDrawFrame;
mForceDrawFrame = false;
canUnblockUiThread = syncFrameState(info);
- canDrawThisFrame = info.out.canDrawThisFrame;
+ canDrawThisFrame = !info.out.skippedFrameReason.has_value();
solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
if (mFrameCommitCallback) {
@@ -192,11 +192,12 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
if (CC_UNLIKELY(!hasTarget || !canDraw)) {
if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
} else {
// If we have a surface but can't draw we must be stopped
mSyncResult |= SyncResult::ContextIsStopped;
+ info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped;
}
- info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
@@ -204,7 +205,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
mSyncResult |= SyncResult::UIRedrawRequired;
}
}
- if (!info.out.canDrawThisFrame) {
+ if (info.out.skippedFrameReason) {
mSyncResult |= SyncResult::FrameDropped;
}
// If prepareTextures is false, we ran out of texture cache space
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index b34da5153a72..2362331aca26 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -24,6 +24,7 @@
#include <vector>
#include "../Properties.h"
+#include "RenderThread.h"
#include "thread/CommonPool.h"
using namespace std::chrono_literals;
@@ -62,24 +63,26 @@ HintSessionWrapper::~HintSessionWrapper() {
}
void HintSessionWrapper::destroy() {
- if (mHintSessionFuture.valid()) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture.has_value()) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
}
if (mHintSession) {
mBinding->closeSession(mHintSession);
mSessionValid = true;
mHintSession = nullptr;
}
+ mResetsSinceLastReport = 0;
}
bool HintSessionWrapper::init() {
if (mHintSession != nullptr) return true;
-
// If we're waiting for the session
- if (mHintSessionFuture.valid()) {
+ if (mHintSessionFuture.has_value()) {
// If the session is here
- if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
if (mHintSession != nullptr) {
mSessionValid = true;
return true;
@@ -107,12 +110,13 @@ bool HintSessionWrapper::init() {
tids.push_back(mUiThreadId);
tids.push_back(mRenderThreadId);
- // Use a placeholder target value to initialize,
- // this will always be replaced elsewhere before it gets used
- int64_t defaultTargetDurationNanos = 16666667;
+ // Use the cached target value if there is one, otherwise use a default. This is to ensure
+ // the cached target and target in PowerHAL are consistent, and that it updates correctly
+ // whenever there is a change.
+ int64_t targetDurationNanos =
+ mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
- return mBinding->createSession(manager, tids.data(), tids.size(),
- defaultTargetDurationNanos);
+ return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
}
@@ -136,6 +140,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
actualDurationNanos < kSanityCheckUpperBound) {
mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
}
+ mLastFrameNotification = systemTime();
}
void HintSessionWrapper::sendLoadResetHint() {
@@ -155,6 +160,27 @@ void HintSessionWrapper::sendLoadIncreaseHint() {
mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
}
+bool HintSessionWrapper::alive() {
+ return mHintSession != nullptr;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+ return mLastFrameNotification;
+}
+
+// Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr) {
+ nsecs_t lastUpdate = wrapperPtr->getLastUpdate();
+ rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable {
+ if (wrapper->getLastUpdate() == lastUpdate) {
+ wrapper->destroy();
+ }
+ // Ensure the shared_ptr is killed at the end of the method
+ wrapper = nullptr;
+ });
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index f8b876e28d51..41891cd80a42 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -19,6 +19,7 @@
#include <android/performance_hint.h>
#include <future>
+#include <optional>
#include "utils/TimeUtils.h"
@@ -27,6 +28,8 @@ namespace uirenderer {
namespace renderthread {
+class RenderThread;
+
class HintSessionWrapper {
public:
friend class HintSessionWrapperTests;
@@ -40,10 +43,15 @@ public:
void sendLoadIncreaseHint();
bool init();
void destroy();
+ bool alive();
+ nsecs_t getLastUpdate();
+ void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr);
private:
APerformanceHintSession* mHintSession = nullptr;
- std::future<APerformanceHintSession*> mHintSessionFuture;
+ // This needs to work concurrently for testing
+ std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
@@ -57,6 +65,7 @@ private:
static constexpr nsecs_t kResetHintTimeout = 100_ms;
static constexpr int64_t kSanityCheckLowerBound = 100_us;
static constexpr int64_t kSanityCheckUpperBound = 10_s;
+ static constexpr int64_t kDefaultTargetDuration = 16666667;
// Allows easier stub when testing
class HintSessionBinding {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 224c878bf43d..be163bad77a7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,7 +16,13 @@
#include "RenderProxy.h"
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
#include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <ui/GraphicBufferAllocator.h>
+
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
#include "Properties.h"
@@ -29,12 +35,6 @@
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkPicture.h>
-
-#include <pthread.h>
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -323,6 +323,11 @@ void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData, bool reset
}
});
}
+ if (!Properties::isolatedProcess) {
+ std::string grallocInfo;
+ GraphicBufferAllocator::getInstance().dump(grallocInfo);
+ dprintf(fd, "%s\n", grallocInfo.c_str());
+ }
}
void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 0dea941c91f9..f76ea063842f 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
@@ -286,7 +287,7 @@ void RenderThread::requireGlContext() {
auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
auto size = glesVersion ? strlen(glesVersion) : -1;
cacheManager().configureContext(&options, glesVersion, size);
- sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+ sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
}
@@ -357,7 +358,15 @@ void RenderThread::dumpGraphicsMemory(int fd, bool includeProfileData) {
String8 cachesOutput;
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
- dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.c_str());
+ dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str());
+ for (auto&& context : mCacheManager->mCanvasContexts) {
+ context->visitAllRenderNodes([&](const RenderNode& node) {
+ if (node.isTextureView()) {
+ dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight());
+ }
+ });
+ }
+ dprintf(fd, "\n");
}
void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 46698a6fdcc0..22c586248705 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -24,6 +24,9 @@
#include <GrTypes.h>
#include <android/sync.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
@@ -33,9 +36,6 @@
#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -386,25 +386,23 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
}
void VulkanManager::initialize() {
- std::lock_guard _lock{mInitializeLock};
-
- if (mDevice != VK_NULL_HANDLE) {
- return;
- }
+ std::call_once(mInitFlag, [&] {
+ GET_PROC(EnumerateInstanceVersion);
+ uint32_t instanceVersion;
+ LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
+ LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
- GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion;
- LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
- LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+ this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
- this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
+ if (Properties::enablePartialUpdates && Properties::useBufferAge) {
+ mSwapBehavior = SwapBehavior::BufferAge;
+ }
- if (Properties::enablePartialUpdates && Properties::useBufferAge) {
- mSwapBehavior = SwapBehavior::BufferAge;
- }
+ mInitialized = true;
+ });
}
static void onGrContextReleased(void* context) {
@@ -438,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
options.fContextDeleteContext = this;
options.fContextDeleteProc = onGrContextReleased;
- return GrDirectContext::MakeVulkan(backendContext, options);
+ return GrDirectContexts::MakeVulkan(backendContext, options);
}
VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
@@ -518,7 +516,7 @@ Frame VulkanManager::dequeueNextBuffer(VulkanSurface* surface) {
// The following flush blocks the GPU immediately instead of waiting for
// other drawing ops. It seems dequeue_fence is not respected otherwise.
// TODO: remove the flush after finding why backendSemaphore is not working.
- bufferInfo->skSurface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
}
}
}
@@ -586,10 +584,10 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
} else {
semaphore = VK_NULL_HANDLE;
}
- GrSemaphoresSubmitted submitted =
- surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
ALOGE_IF(!context, "Surface is not backed by gpu");
+ GrSemaphoresSubmitted submitted = context->flush(
+ surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
context->submit();
const nsecs_t submissionTime = systemTime();
if (semaphore != VK_NULL_HANDLE) {
@@ -599,10 +597,11 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
// retrieve VkImage used as render target
VkImage image = VK_NULL_HANDLE;
GrBackendRenderTarget backendRenderTarget =
- surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+ SkSurfaces::GetBackendRenderTarget(
+ surface, SkSurfaces::BackendHandleAccess::kFlushRead);
if (backendRenderTarget.isValid()) {
GrVkImageInfo info;
- if (backendRenderTarget.getVkImageInfo(&info)) {
+ if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
image = info.fImage;
} else {
ALOGE("Frame boundary: backend is not vulkan");
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 2be1ffdbc423..dbef7fbd51b2 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -70,7 +70,7 @@ public:
void initialize();
// Quick check to see if the VulkanManager has been initialized.
- bool hasVkContext() { return mDevice != VK_NULL_HANDLE; }
+ bool hasVkContext() { return mInitialized; }
// Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface
VulkanSurface* createSurface(ANativeWindow* window,
@@ -204,7 +204,8 @@ private:
VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
void* mDestroySemaphoreContext = nullptr;
- std::mutex mInitializeLock;
+ std::once_flag mInitFlag;
+ std::atomic_bool mInitialized = false;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 3168cb09291b..20b743bab2c2 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
#include "VulkanSurface.h"
+#include <include/android/SkSurfaceAndroid.h>
#include <GrDirectContext.h>
#include <SkSurface.h>
#include <algorithm>
@@ -24,9 +25,6 @@
#include "VulkanManager.h"
#include "utils/Color.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -470,12 +468,12 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
surfaceProps.pixelGeometry());
}
- bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
+ bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
/*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
- ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
+ ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
mNativeBuffers[idx].dequeue_fence.release());
mNativeBuffers[idx].dequeued = false;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ede8faa..ad963dd913cf 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
#include "DeferredLayerUpdater.h"
#include "hwui/Paint.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <pipeline/skia/SkiaOpenGLPipeline.h>
#include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@ SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
return outlineInLocalCoord;
}
+SkFont TestUtils::defaultFont() {
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+ Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+ LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+ return SkFont(sk_ref_sp(skTypeface));
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe59d3bc..0ede902b1b95 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
#include <SkBitmap.h>
#include <SkColor.h>
+#include <SkFont.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
@@ -61,18 +62,10 @@ namespace uirenderer {
ADD_FAILURE() << "ClipState not a rect"; \
}
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
- TEST(test_case_name, test_name##_##pipeline) { \
- RenderPipelineType oldType = Properties::getRenderPipelineType(); \
- Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
- functionCall; \
- Properties::overrideRenderPipelineType(oldType); \
- };
-
-#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
- INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \
- TestUtils::runOnRenderThread( \
- test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name) \
+ TEST(test_case_name, test_name) { \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ }
/**
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
@@ -83,21 +76,7 @@ namespace uirenderer {
public: \
static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
- /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
- renderthread::RenderThread& renderThread)
-
-/**
- * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
- */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name); \
/* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
/* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
@@ -307,13 +286,21 @@ public:
int destroyed = 0;
int removeOverlays = 0;
int glesDraw = 0;
+ int vkInitialize = 0;
+ int vkDraw = 0;
+ int vkPostDraw = 0;
};
static void expectOnRenderThread(const std::string_view& function = "unknown") {
EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
}
- static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+ static int createMockFunctor() {
+ const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+ return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+ }
+
+ static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
auto callbacks = WebViewFunctorCallbacks{
.onSync =
[](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,15 +332,30 @@ public:
sMockFunctorCounts[functor].glesDraw++;
};
break;
- default:
- ADD_FAILURE();
- return WebViewFunctorCallbacks{};
+ case RenderMode::Vulkan:
+ callbacks.vk.initialize = [](int functor, void* data,
+ const VkFunctorInitParams& params) {
+ expectOnRenderThread("initialize");
+ sMockFunctorCounts[functor].vkInitialize++;
+ };
+ callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+ const WebViewOverlayData& overlayParams) {
+ expectOnRenderThread("draw");
+ sMockFunctorCounts[functor].vkDraw++;
+ };
+ callbacks.vk.postDraw = [](int functor, void* data) {
+ expectOnRenderThread("postDraw");
+ sMockFunctorCounts[functor].vkPostDraw++;
+ };
+ break;
}
return callbacks;
}
static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+ static SkFont defaultFont();
+
private:
static std::unordered_map<int, CallCounts> sMockFunctorCounts;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d9468cd88..97d4c8214cde 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@ class ListViewAnimation : public TestListViewSceneBase {
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490c1d39..159541c11c64 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@ private:
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb6897c..e227999c2432 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
* limitations under the License.
*/
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
#include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+
+#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
using namespace android;
+using namespace android::base;
using namespace android::uirenderer;
using namespace android::uirenderer::test;
@@ -69,6 +71,9 @@ OPTIONS:
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --benchmark_list_tests Lists the tests that would run but does not run them
+ --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+ that doesn't match the given regex is run
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--skip-leak-check Skips the memory leak check
--report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@ static bool setBenchmarkFormat(const char* format) {
if (!strcmp(format, "tabular")) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
+ // We cannot print the leak check if outputing to JSON as that will break
+ // JSON parsers since it's not JSON-formatted
+ gRunLeakCheck = false;
gBenchmarkReporter.reset(new benchmark::JSONReporter());
} else {
fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@ static bool setRenderer(const char* renderer) {
return true;
}
+static void addTestsThatMatchFilter(std::string spec) {
+ if (spec.empty() || spec == "all") {
+ spec = "."; // Regexp that matches all benchmarks
+ }
+ bool isNegativeFilter = false;
+ if (spec[0] == '-') {
+ spec.replace(0, 1, "");
+ isNegativeFilter = true;
+ }
+ std::regex re(spec, std::regex_constants::extended);
+ for (auto& iter : TestScene::testMap()) {
+ if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+ (!isNegativeFilter && std::regex_search(iter.first, re))) {
+ gRunTests.push_back(iter.second);
+ }
+ }
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -170,6 +196,8 @@ enum {
ReportFrametime,
CpuSet,
BenchmarkFormat,
+ BenchmarkListTests,
+ BenchmarkFilter,
Onscreen,
Offscreen,
Renderer,
@@ -179,14 +207,16 @@ enum {
}
static const struct option LONG_OPTIONS[] = {
- {"frames", required_argument, nullptr, 'f'},
- {"repeat", required_argument, nullptr, 'r'},
+ {"count", required_argument, nullptr, 'c'},
+ {"runs", required_argument, nullptr, 'r'},
{"help", no_argument, nullptr, 'h'},
{"list", no_argument, nullptr, LongOpts::List},
{"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
{"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
{"cpuset", required_argument, nullptr, LongOpts::CpuSet},
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+ {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+ {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@ static const struct option LONG_OPTIONS[] = {
static const char* SHORT_OPTIONS = "c:r:h";
void parseOptions(int argc, char* argv[]) {
+ benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
int c;
bool error = false;
+ bool listTestsOnly = false;
+ bool testsAreFiltered = false;
opterr = 0;
while (true) {
@@ -272,6 +306,21 @@ void parseOptions(int argc, char* argv[]) {
}
break;
+ case LongOpts::BenchmarkListTests:
+ if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+ listTestsOnly = true;
+ }
+ break;
+
+ case LongOpts::BenchmarkFilter:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ addTestsThatMatchFilter(optarg);
+ testsAreFiltered = true;
+ break;
+
case LongOpts::Renderer:
if (!optarg) {
error = true;
@@ -346,11 +395,18 @@ void parseOptions(int argc, char* argv[]) {
}
}
} while (optind < argc);
- } else {
+ } else if (gRunTests.empty() && !testsAreFiltered) {
for (auto& iter : TestScene::testMap()) {
gRunTests.push_back(iter.second);
}
}
+
+ if (listTestsOnly) {
+ for (auto& iter : gRunTests) {
+ std::cout << iter.name << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+ }
}
int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 138b3efd10ed..b8b3f0aa5229 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -46,7 +46,7 @@ RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
- // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+ // SkImages::BorrowTextureFrom should fail if given null GrDirectContext.
textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index 2b90bda87ecd..89d00d3c0dcb 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -20,7 +20,8 @@
#include "renderthread/EglManager.h"
#include "tests/common/TestUtils.h"
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
using namespace android;
@@ -34,7 +35,7 @@ static size_t getCacheUsage(GrDirectContext* grContext) {
}
// TOOD(258700630): fix this test and re-enable
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
+RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) {
int32_t width = DeviceInfo::get()->getWidth();
int32_t height = DeviceInfo::get()->getHeight();
GrDirectContext* grContext = renderThread.getGrContext();
@@ -46,8 +47,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
SkImageInfo info = SkImageInfo::MakeA8(width, height);
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
- info);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes,
+ info);
surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
grContext->flushAndSubmit();
@@ -57,9 +58,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
// create an image and pin it so that we have something with a unique key in the cache
sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
- sk_sp<SkImage> image = bitmap->makeImage();
- ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
-
+ sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood.
+ ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get()));
// attempt to trim all memory while we still hold strong refs
renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
@@ -71,7 +71,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
}
// unpin the image which should add a unique purgeable key to the cache
- SkImage_unpinAsTexture(image.get(), grContext);
+ skgpu::ganesh::UnpinTexture(grContext, image.get());
// verify that we have enough purgeable bytes
const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e32f8ea..47a41057cac9 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
#include "AnimationContext.h"
#include "IContextFactory.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
#include "tests/common/TestUtils.h"
using namespace android;
@@ -42,3 +43,38 @@ RENDERTHREAD_TEST(CanvasContext, create) {
canvasContext->destroy();
}
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+ auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+ });
+ ASSERT_TRUE(node->isValid());
+ EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+ auto& cacheManager = renderThread.cacheManager();
+ EXPECT_TRUE(cacheManager.areAllContextsStopped());
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(
+ CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+ canvasContext->buildLayer(node.get());
+ EXPECT_TRUE(node->hasLayer());
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ EXPECT_TRUE(instance->hasVkContext());
+ } else {
+ ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+ }
+ }
+ renderThread.destroyRenderingContext();
+ EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ ADD_FAILURE() << "VulkanManager still exists";
+ EXPECT_FALSE(instance->hasVkContext());
+ }
+ }
+}
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 0c389bfe8b71..cfa18ae01b4f 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -40,7 +40,7 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) {
// push the deferred updates to the layer
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
- sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
+ sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap);
layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
// the backing layer should now have all the properties applied.
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 623be1e3f3f3..10a740a1f803 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -23,9 +23,11 @@
#include <chrono>
#include "Properties.h"
+#include "tests/common/TestUtils.h"
using namespace testing;
using namespace std::chrono_literals;
+using namespace android::uirenderer::renderthread;
APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
@@ -42,6 +44,9 @@ public:
protected:
std::shared_ptr<HintSessionWrapper> mWrapper;
+ std::promise<int> blockDestroyCallUntil;
+ std::promise<int> waitForDestroyFinished;
+
class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
public:
void init() override;
@@ -53,11 +58,17 @@ protected:
MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+ // Needs to be on the binding so it can be accessed from static methods
+ std::promise<int> allowCreationToFinish;
};
// Must be static so it can have function pointers we can point to with static methods
static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+ static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
+ void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
+ void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
+
// Must be static so we can point to them as normal fn pointers with HintSessionBinding
static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
@@ -65,6 +76,12 @@ protected:
int64_t initialTarget) {
return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
}
+ static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ sMockBinding->allowCreationToFinish.get_future().wait();
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
const int32_t* ids, size_t idsSize,
int64_t initialTarget) {
@@ -85,7 +102,21 @@ protected:
static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
sMockBinding->fakeSendHint(session, hintId);
};
- void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+ void waitForWrapperReady() {
+ if (mWrapper->mHintSessionFuture.has_value()) {
+ mWrapper->mHintSessionFuture->wait();
+ }
+ }
+ void scheduleDelayedDestroyManaged() {
+ TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
+ // Guaranteed to be scheduled first, allows destruction to start
+ rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
+ // Guaranteed to be scheduled second, destroys the session
+ mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
+ // This is guaranteed to be queued after the destroy, signals that destruction is done
+ rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
+ });
+ }
};
std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
@@ -113,6 +144,7 @@ void HintSessionWrapperTests::MockHintSessionBinding::init() {
}
void HintSessionWrapperTests::TearDown() {
+ // Ensure that anything running on RT is completely finished
mWrapper = nullptr;
sMockBinding = nullptr;
}
@@ -122,6 +154,7 @@ TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
sMockBinding->createSession = stubSlowCreateSession;
mWrapper->init();
mWrapper = nullptr;
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
}
TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
@@ -148,4 +181,162 @@ TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
mWrapper->sendLoadResetHint();
}
+TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to ensure the wrapper grabs the promise value
+ mWrapper->init();
+
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // Schedule delayed destruction, allow it to run, and check when it's done
+ scheduleDelayedDestroyManaged();
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+ // If we then delete the wrapper, it shouldn't close the session again
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
+ mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens after
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow destruction to happen first
+ allowDelayedDestructionToStart();
+
+ // Make sure destruction has had time to happen
+ std::this_thread::sleep_for(50ms);
+
+ // Then, allow creation to finish after delayed destroy runs
+ allowCreationToFinish();
+
+ // Wait for destruction to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens before
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow creation to happen first
+ allowCreationToFinish();
+
+ // Make sure creation has had time to happen
+ waitForWrapperReady();
+
+ // Then allow destruction to happen after creation is done
+ allowDelayedDestructionToStart();
+
+ // Wait for it to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, report an actual duration
+ mWrapper->reportActualWorkDuration(5_ms);
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+ .Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, send a load_up hint
+ mWrapper->sendLoadIncreaseHint();
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+ // Then, run the delayed deletion after manually killing the session
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close again and is still dead
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
} // namespace android::uirenderer::renderthread \ No newline at end of file
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 596bd37e4cf5..073a8357e574 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-#include <VectorDrawable.h>
-#include <gtest/gtest.h>
-
#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
+#include <VectorDrawable.h>
+#include <gtest/gtest.h>
+#include <include/effects/SkImageFilters.h>
#include <string.h>
+
#include "AnimationContext.h"
#include "DamageAccumulator.h"
#include "FatalTestCanvas.h"
#include "IContextFactory.h"
-#include "hwui/Paint.h"
#include "RecordingCanvas.h"
#include "SkiaCanvas.h"
+#include "hwui/Paint.h"
+#include "pipeline/skia/BackdropFilterDrawable.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaPipeline.h"
@@ -141,7 +143,7 @@ TEST(RenderNodeDrawable, zReorder) {
}
TEST(RenderNodeDrawable, composeOnLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
@@ -152,7 +154,7 @@ TEST(RenderNodeDrawable, composeOnLayer) {
});
// attach a layer to the render node
- auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
auto canvas2 = surfaceLayer->getCanvas();
canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
rootNode->setLayerSurface(surfaceLayer);
@@ -187,7 +189,7 @@ static SkMatrix getRecorderMatrix(const SkiaRecordingCanvas& recorder) {
}
TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
- auto surface = SkSurface::MakeRasterN32Premul(400, 800);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
@@ -353,7 +355,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
EXPECT_EQ(3, canvas.getIndex());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) {
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
@@ -417,7 +419,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
EXPECT_EQ(2, canvas.getDrawCounter());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
/ \
@@ -1050,7 +1052,7 @@ TEST(RenderNodeDrawable, renderNode) {
}
// Verify that layers are composed with linear filtering.
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
+RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) {
static const int CANVAS_WIDTH = 1;
static const int CANVAS_HEIGHT = 1;
static const int LAYER_WIDTH = 1;
@@ -1074,7 +1076,8 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
});
layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
+ layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH,
+ LAYER_HEIGHT)));
FrameTestCanvas canvas;
RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
@@ -1167,7 +1170,7 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) {
}
// Draw a vector drawable twice but with different bounds and verify correct bounds are used.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 200;
class VectorDrawableTestCanvas : public TestCanvasBase {
@@ -1210,3 +1213,77 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
canvas.drawDrawable(&drawable);
EXPECT_EQ(2, canvas.mDrawCounter);
}
+
+// Verify drawing logics for BackdropFilterDrawable
+RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) {
+ static const int CANVAS_WIDTH = 100;
+ static const int CANVAS_HEIGHT = 200;
+ class SimpleTestCanvas : public TestCanvasBase {
+ public:
+ SkRect mDstBounds;
+ SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ // did nothing.
+ }
+
+ // called when BackdropFilterDrawable is drawn.
+ void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
+ mDrawCounter++;
+ mDstBounds = dst;
+ }
+ };
+ class SimpleLayer : public SkSurface_Base {
+ public:
+ SimpleLayer()
+ : SkSurface_Base(SkImageInfo::MakeN32Premul(CANVAS_WIDTH, CANVAS_HEIGHT), nullptr) {
+ }
+ virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override {
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT);
+ bitmap.setImmutable();
+ return bitmap.asImage();
+ }
+ SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); }
+ sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
+ bool onCopyOnWrite(ContentChangeMode) override { return true; }
+ void onWritePixels(const SkPixmap&, int x, int y) {}
+ };
+
+ auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ Paint());
+ });
+
+ sk_sp<SkSurface> surface(new SimpleLayer());
+ auto* canvas = reinterpret_cast<SimpleTestCanvas*>(surface->getCanvas());
+ RenderNodeDrawable drawable(node.get(), canvas, true);
+ BackdropFilterDrawable backdropDrawable(node.get(), canvas);
+ canvas->drawDrawable(&drawable);
+ canvas->drawDrawable(&backdropDrawable);
+ // no backdrop filter, skip drawing.
+ EXPECT_EQ(0, canvas->mDrawCounter);
+
+ sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr));
+ node->animatorProperties().mutateLayerProperties().setBackdropImageFilter(filter.get());
+ canvas->drawDrawable(&drawable);
+ canvas->drawDrawable(&backdropDrawable);
+ // backdrop filter is set, ok to draw.
+ EXPECT_EQ(1, canvas->mDrawCounter);
+ EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds);
+
+ canvas->translate(30, 30);
+ canvas->drawDrawable(&drawable);
+ canvas->drawDrawable(&backdropDrawable);
+ // the drawable is still visible, ok to draw.
+ EXPECT_EQ(2, canvas->mDrawCounter);
+ EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds);
+
+ canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT);
+ canvas->drawDrawable(&drawable);
+ canvas->drawDrawable(&backdropDrawable);
+ // the drawable is invisible, skip drawing.
+ EXPECT_EQ(2, canvas->mDrawCounter);
+}
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4a4111..8273524167f9 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@ TEST(RenderNode, multiTreeValidity) {
}
TEST(RenderNode, releasedCallback) {
- int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor = TestUtils::createMockFunctor();
auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
canvas.drawWebViewFunctor(functor);
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 9aa2e1db4461..0f8bd1368f5a 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -370,9 +370,9 @@ TEST(ShaderCacheTest, testCacheValidation) {
}
using namespace android::uirenderer;
-RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
- // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+ // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
}
if (!folderExist(getExternalStorageFolder())) {
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 87c52161d68e..e53fcaa474e2 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,7 @@ using namespace android;
using namespace android::uirenderer;
TEST(SkiaCanvas, drawShadowLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
SkiaCanvas canvas(surface->getCanvas());
// clear to white
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c5d9cc..064d42ec8941 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@ TEST(SkiaDisplayList, reset) {
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
WebViewFunctor_release(functor1);
skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@ TEST(SkiaDisplayList, syncContexts) {
SkCanvas dummyCanvas;
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
auto& counts = TestUtils::countsForFunctor(functor1);
skiaDL.mChildFunctors.push_back(
skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
@@ -131,6 +129,33 @@ TEST(SkiaDisplayList, syncContexts) {
EXPECT_EQ(counts.destroyed, 1);
}
+TEST(SkiaDisplayList, recordMutableBitmap) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
+TEST(SkiaDisplayList, recordMutableBitmapInShader) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+ Paint paint;
+ paint.setShader(bitmap->makeImage()->makeShader(sampling));
+ canvas.drawPaint(paint);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@@ -138,7 +163,7 @@ public:
}
};
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
@@ -197,7 +222,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
canvasContext->destroy();
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 4d0595e03da6..3ded540c3152 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -42,7 +42,7 @@ using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -54,7 +54,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
@@ -62,7 +62,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
Paint greenPaint;
@@ -76,7 +76,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
renderNodes.push_back(halfGreenNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -89,7 +89,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -100,7 +100,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
renderNodes.push_back(redNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -111,12 +111,12 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
- auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
redNode->setLayerSurface(surfaceLayer1);
@@ -127,7 +127,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
});
- auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
blueNode->setLayerSurface(surfaceLayer2);
@@ -154,7 +154,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
blueNode->setLayerSurface(sk_sp<SkSurface>());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
ScopedProperty<bool> prop(Properties::debugOverdraw, true);
auto whiteNode = TestUtils::createSkiaNode(
@@ -169,7 +169,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
// empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw
android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
// Initialize the canvas to blue.
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
@@ -227,7 +227,7 @@ public:
};
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
class DeferTestCanvas : public SkCanvas {
public:
DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -297,7 +297,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
EXPECT_EQ(4, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class ClippedTestCanvas : public SkCanvas {
@@ -330,7 +330,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
}
// Test renderFrame with a dirty clip and a pre-transform matrix.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 100;
static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
@@ -366,7 +366,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
static const int CANVAS_WIDTH = 50;
static const int CANVAS_HEIGHT = 50;
class ClipReplaceTestCanvas : public SkCanvas {
@@ -396,7 +396,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
+RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
test::TestContext context;
auto surface = context.surface();
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
@@ -410,7 +410,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
EXPECT_TRUE(pipeline->isSurfaceReady());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
+RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
// create a pipeline and add a picture callback
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
int callbackCount = 0;
@@ -428,7 +428,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 000000000000..db2be20936fb
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+ std::vector<minikin::FontVariation>());
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
+ fonts.push_back(minikin::Font::Builder(font).build());
+ return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+ return Typeface::createFromFamilies(
+ std::vector<std::shared_ptr<minikin::FontFamily>>(
+ {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+ return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+ 0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+ // Create canvas
+ SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+ sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ SkiaCanvas canvas(skBitmap);
+
+ // Create minikin::Layout
+ std::unique_ptr<Typeface> typeface(makeTypeface());
+ minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, paint, f);
+ return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // the text is "abc"
+ DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+ EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is 恂恄恆 in Japanese
+ DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is a恄c. The only middle of the character is Japanese.
+ DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+ // We use the bottom, thicker line as underline. Here, use Noto's one.
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+} // namespace
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7069ff..5e8f13d261c7 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
using namespace android;
using namespace android::uirenderer;
+#define ASSUME_GLES() \
+ if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+ GTEST_SKIP() << "Not in GLES, skipping test"
+
TEST(WebViewFunctor, createDestroyGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
WebViewFunctor_release(functor);
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@ TEST(WebViewFunctor, createDestroyGLES) {
}
TEST(WebViewFunctor, createSyncHandleGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@ TEST(WebViewFunctor, createSyncHandleGLES) {
}
TEST(WebViewFunctor, createSyncDrawGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@ TEST(WebViewFunctor, createSyncDrawGLES) {
EXPECT_EQ(1, counts.destroyed);
}
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 10c874ec3f12..76cbc8abc808 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <getopt.h>
+#include <signal.h>
#include "Properties.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
#include "hwui/Typeface.h"
#include "tests/common/LeakChecker.h"
-#include <signal.h>
-
using namespace std;
using namespace android;
using namespace android::uirenderer;
@@ -45,6 +45,57 @@ static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) {
raise(sig);
}
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+ Reserved = 255,
+ Renderer,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+ {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}};
+
+static RenderPipelineType parseRenderer(const char* renderer) {
+ // Anything that's not skiavk is skiagl
+ if (!strcmp(renderer, "skiavk")) {
+ return RenderPipelineType::SkiaVulkan;
+ }
+ return RenderPipelineType::SkiaGL;
+}
+
+struct Options {
+ RenderPipelineType renderer = RenderPipelineType::SkiaGL;
+};
+
+Options parseOptions(int argc, char* argv[]) {
+ int c;
+ opterr = 0;
+ Options opts;
+
+ while (true) {
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index);
+
+ if (c == -1) break;
+
+ switch (c) {
+ case 0:
+ // Option set a flag, don't need to do anything
+ // (although none of the current LONG_OPTIONS do this...)
+ break;
+
+ case LongOpts::Renderer:
+ opts.renderer = parseRenderer(optarg);
+ break;
+ }
+ }
+ return opts;
+}
+
class TypefaceEnvironment : public testing::Environment {
public:
virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); }
@@ -64,8 +115,9 @@ int main(int argc, char* argv[]) {
// Avoid talking to SF
Properties::isolatedProcess = true;
- // Default to GLES (Vulkan-aware tests will override this)
- Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+
+ auto opts = parseOptions(argc, argv);
+ Properties::overrideRenderPipelineType(opts.renderer);
// Run the tests
testing::InitGoogleTest(&argc, argv);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 6c0fd5f65359..5ce990fdeb82 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -23,6 +23,12 @@ package {
cc_library_shared {
name: "libinputservice",
+ defaults: [
+ // Build using the same flags and configurations as inputflinger.
+ "inputflinger_defaults",
+ ],
+ host_supported: false,
+
srcs: [
"PointerController.cpp",
"PointerControllerContext.cpp",
@@ -50,12 +56,4 @@ cc_library_shared {
],
include_dirs: ["frameworks/native/services"],
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wthread-safety",
- ],
-
}
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index c3ad7670d473..6a465442c2b4 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -47,7 +47,7 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context)
mLocked.pointerX = 0;
mLocked.pointerY = 0;
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
- mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
+ mLocked.pointerSprite = mContext.getSpriteController().createSprite();
mLocked.updatePointerIcon = false;
mLocked.requestedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
@@ -325,8 +325,8 @@ bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(
}
if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
mLocked.animationFrameIndex += incr;
@@ -336,7 +336,7 @@ bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(
}
mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
// Keep animating.
return true;
@@ -346,8 +346,8 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
if (!mLocked.viewport.isValid()) {
return;
}
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
@@ -392,7 +392,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
mLocked.updatePointerIcon = false;
}
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index e21d6fb2fe14..abd928486607 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -63,10 +63,10 @@ void PointerController::DisplayInfoListener::onPointerControllerDestroyed() {
std::shared_ptr<PointerController> PointerController::create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) {
+ SpriteController& spriteController, bool enabled) {
// using 'new' to access non-public constructor
std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
- new PointerController(policy, looper, spriteController));
+ new PointerController(policy, looper, spriteController, enabled));
/*
* Now we need to hook up the constructed PointerController object to its callbacks.
@@ -85,10 +85,10 @@ std::shared_ptr<PointerController> PointerController::create(
}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper,
- const sp<SpriteController>& spriteController)
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled)
: PointerController(
- policy, looper, spriteController,
+ policy, looper, spriteController, enabled,
[](const sp<android::gui::WindowInfosListener>& listener) {
SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
},
@@ -97,13 +97,13 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
}) {}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper,
- const sp<SpriteController>& spriteController,
- WindowListenerConsumer registerListener,
+ const sp<Looper>& looper, SpriteController& spriteController,
+ bool enabled, WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener)
- : mContext(policy, looper, spriteController, *this),
+ : mEnabled(enabled),
+ mContext(policy, looper, spriteController, *this),
mCursorController(mContext),
- mDisplayInfoListener(new DisplayInfoListener(this)),
+ mDisplayInfoListener(sp<DisplayInfoListener>::make(this)),
mUnregisterWindowInfosListener(std::move(unregisterListener)) {
std::scoped_lock lock(getLock());
mLocked.presentation = Presentation::SPOT;
@@ -121,10 +121,14 @@ std::mutex& PointerController::getLock() const {
}
std::optional<FloatRect> PointerController::getBounds() const {
+ if (!mEnabled) return {};
+
return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -136,6 +140,8 @@ void PointerController::move(float deltaX, float deltaY) {
}
void PointerController::setPosition(float x, float y) {
+ if (!mEnabled) return;
+
const int32_t displayId = mCursorController.getDisplayId();
vec2 transformed;
{
@@ -147,6 +153,11 @@ void PointerController::setPosition(float x, float y) {
}
FloatPoint PointerController::getPosition() const {
+ if (!mEnabled) {
+ return FloatPoint{AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION};
+ }
+
const int32_t displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
@@ -157,20 +168,28 @@ FloatPoint PointerController::getPosition() const {
}
int32_t PointerController::getDisplayId() const {
+ if (!mEnabled) return ADISPLAY_ID_NONE;
+
return mCursorController.getDisplayId();
}
void PointerController::fade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.fade(transition);
}
void PointerController::unfade(Transition transition) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.unfade(transition);
}
void PointerController::setPresentation(Presentation presentation) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
if (mLocked.presentation == presentation) {
@@ -195,6 +214,8 @@ void PointerController::setPresentation(Presentation presentation) {
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -218,6 +239,8 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t
}
void PointerController::clearSpots() {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
clearSpotsLocked();
}
@@ -279,11 +302,15 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
}
void PointerController::updatePointerIcon(PointerIconStyle iconId) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.updatePointerIcon(iconId);
}
void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+ if (!mEnabled) return;
+
std::scoped_lock lock(getLock());
mCursorController.setCustomPointerIcon(icon);
}
@@ -292,7 +319,7 @@ void PointerController::doInactivityTimeout() {
fade(Transition::GRADUAL);
}
-void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
+void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
std::unordered_set<int32_t> displayIdSet;
for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
@@ -327,8 +354,12 @@ const ui::Transform& PointerController::getTransformForDisplayLocked(int display
return it != di.end() ? it->transform : kIdentityTransform;
}
-void PointerController::dump(std::string& dump) {
- dump += INDENT "PointerController:\n";
+std::string PointerController::dump() {
+ if (!mEnabled) {
+ return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n";
+ }
+
+ std::string dump = INDENT "PointerController:\n";
std::scoped_lock lock(getLock());
dump += StringPrintf(INDENT2 "Presentation: %s\n",
ftl::enum_string(mLocked.presentation).c_str());
@@ -341,6 +372,7 @@ void PointerController::dump(std::string& dump) {
for (const auto& [_, spotController] : mLocked.spotControllers) {
spotController.dump(dump, INDENT3);
}
+ return dump;
}
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 62ee74331302..aa7ca3c52ecf 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@ class PointerController : public PointerControllerInterface {
public:
static std::shared_ptr<PointerController> create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController);
+ SpriteController& spriteController, bool enabled);
~PointerController() override;
@@ -70,12 +70,12 @@ public:
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
void reloadPointerResources();
- void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
+ void onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports);
void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos)
REQUIRES(getLock());
- void dump(std::string& dump);
+ std::string dump();
protected:
using WindowListenerConsumer =
@@ -83,13 +83,13 @@ protected:
// Constructor used to test WindowInfosListener registration.
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController,
+ SpriteController& spriteController, bool enabled,
WindowListenerConsumer registerListener,
WindowListenerConsumer unregisterListener);
private:
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController);
+ SpriteController& spriteController, bool enabled);
friend PointerControllerContext::LooperCallback;
friend PointerControllerContext::MessageHandler;
@@ -100,6 +100,8 @@ private:
// we use the DisplayInfoListener's lock in PointerController.
std::mutex& getLock() const;
+ const bool mEnabled;
+
PointerControllerContext mContext;
MouseCursorController mCursorController;
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index f30e8d8e33a5..15c35176afce 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -32,12 +32,12 @@ namespace android {
PointerControllerContext::PointerControllerContext(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
- const sp<SpriteController>& spriteController, PointerController& controller)
+ SpriteController& spriteController, PointerController& controller)
: mPolicy(policy),
mLooper(looper),
mSpriteController(spriteController),
- mHandler(new MessageHandler()),
- mCallback(new LooperCallback()),
+ mHandler(sp<MessageHandler>::make()),
+ mCallback(sp<LooperCallback>::make()),
mController(controller),
mAnimator(*this) {
std::scoped_lock lock(mLock);
@@ -93,7 +93,7 @@ sp<PointerControllerPolicyInterface> PointerControllerContext::getPolicy() {
return mPolicy;
}
-sp<SpriteController> PointerControllerContext::getSpriteController() {
+SpriteController& PointerControllerContext::getSpriteController() {
return mSpriteController;
}
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index f6f5d3bc51bd..98c3988e7df4 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -92,7 +92,7 @@ public:
class PointerControllerContext {
public:
PointerControllerContext(const sp<PointerControllerPolicyInterface>& policy,
- const sp<Looper>& looper, const sp<SpriteController>& spriteController,
+ const sp<Looper>& looper, SpriteController& spriteController,
PointerController& controller);
~PointerControllerContext();
@@ -109,7 +109,7 @@ public:
void setCallbackController(std::shared_ptr<PointerController> controller);
sp<PointerControllerPolicyInterface> getPolicy();
- sp<SpriteController> getSpriteController();
+ SpriteController& getSpriteController();
void handleDisplayEvents();
@@ -163,7 +163,7 @@ private:
sp<PointerControllerPolicyInterface> mPolicy;
sp<Looper> mLooper;
- sp<SpriteController> mSpriteController;
+ SpriteController& mSpriteController;
sp<MessageHandler> mHandler;
sp<LooperCallback> mCallback;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 130b204954b4..6dc45a6aebec 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -31,12 +31,19 @@ SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLaye
ParentSurfaceProvider parentSurfaceProvider)
: mLooper(looper),
mOverlayLayer(overlayLayer),
+ mHandler(sp<Handler>::make()),
mParentSurfaceProvider(std::move(parentSurfaceProvider)) {
- mHandler = new WeakMessageHandler(this);
mLocked.transactionNestingCount = 0;
mLocked.deferredSpriteUpdate = false;
}
+void SpriteController::setHandlerController(
+ const std::shared_ptr<android::SpriteController>& controller) {
+ // Initialize the weak message handler outside the constructor, because we cannot get a shared
+ // pointer to self in the constructor.
+ mHandler->spriteController = controller;
+}
+
SpriteController::~SpriteController() {
mLooper->removeMessages(mHandler);
@@ -47,7 +54,7 @@ SpriteController::~SpriteController() {
}
sp<Sprite> SpriteController::createSprite() {
- return new SpriteImpl(this);
+ return sp<SpriteImpl>::make(*this);
}
void SpriteController::openTransaction() {
@@ -65,7 +72,7 @@ void SpriteController::closeTransaction() {
mLocked.transactionNestingCount -= 1;
if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
mLocked.deferredSpriteUpdate = false;
- mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES));
}
}
@@ -76,7 +83,7 @@ void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
if (mLocked.transactionNestingCount != 0) {
mLocked.deferredSpriteUpdate = true;
} else {
- mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_UPDATE_SPRITES));
}
}
}
@@ -85,18 +92,7 @@ void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceCon
bool wasEmpty = mLocked.disposedSurfaces.empty();
mLocked.disposedSurfaces.push_back(surfaceControl);
if (wasEmpty) {
- mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
- }
-}
-
-void SpriteController::handleMessage(const Message& message) {
- switch (message.what) {
- case MSG_UPDATE_SPRITES:
- doUpdateSprites();
- break;
- case MSG_DISPOSE_SURFACES:
- doDisposeSurfaces();
- break;
+ mLooper->sendMessage(mHandler, Message(Handler::MSG_DISPOSE_SURFACES));
}
}
@@ -327,7 +323,7 @@ void SpriteController::doDisposeSurfaces() {
void SpriteController::ensureSurfaceComposerClient() {
if (mSurfaceComposerClient == NULL) {
- mSurfaceComposerClient = new SurfaceComposerClient();
+ mSurfaceComposerClient = sp<SurfaceComposerClient>::make();
}
}
@@ -353,25 +349,41 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height
return surfaceControl;
}
-// --- SpriteController::SpriteImpl ---
+// --- SpriteController::Handler ---
-SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
- mController(controller) {
+void SpriteController::Handler::handleMessage(const android::Message& message) {
+ auto controller = spriteController.lock();
+ if (!controller) {
+ return;
+ }
+
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ controller->doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ controller->doDisposeSurfaces();
+ break;
+ }
}
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(SpriteController& controller) : mController(controller) {}
+
SpriteController::SpriteImpl::~SpriteImpl() {
- AutoMutex _m(mController->mLock);
+ AutoMutex _m(mController.mLock);
// Let the controller take care of deleting the last reference to sprite
// surfaces so that we do not block the caller on an IPC here.
if (mLocked.state.surfaceControl != NULL) {
- mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mController.disposeSurfaceLocked(mLocked.state.surfaceControl);
mLocked.state.surfaceControl.clear();
}
}
void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
uint32_t dirty;
if (icon.isValid()) {
@@ -401,7 +413,7 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
}
void SpriteController::SpriteImpl::setVisible(bool visible) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.visible != visible) {
mLocked.state.visible = visible;
@@ -410,7 +422,7 @@ void SpriteController::SpriteImpl::setVisible(bool visible) {
}
void SpriteController::SpriteImpl::setPosition(float x, float y) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
mLocked.state.positionX = x;
@@ -420,7 +432,7 @@ void SpriteController::SpriteImpl::setPosition(float x, float y) {
}
void SpriteController::SpriteImpl::setLayer(int32_t layer) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.layer != layer) {
mLocked.state.layer = layer;
@@ -429,7 +441,7 @@ void SpriteController::SpriteImpl::setLayer(int32_t layer) {
}
void SpriteController::SpriteImpl::setAlpha(float alpha) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.alpha != alpha) {
mLocked.state.alpha = alpha;
@@ -439,7 +451,7 @@ void SpriteController::SpriteImpl::setAlpha(float alpha) {
void SpriteController::SpriteImpl::setTransformationMatrix(
const SpriteTransformationMatrix& matrix) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.transformationMatrix != matrix) {
mLocked.state.transformationMatrix = matrix;
@@ -448,7 +460,7 @@ void SpriteController::SpriteImpl::setTransformationMatrix(
}
void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
- AutoMutex _l(mController->mLock);
+ AutoMutex _l(mController.mLock);
if (mLocked.state.displayId != displayId) {
mLocked.state.displayId = displayId;
@@ -461,7 +473,7 @@ void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
mLocked.state.dirty |= dirty;
if (!wasDirty) {
- mController->invalidateSpriteLocked(this);
+ mController.invalidateSpriteLocked(sp<SpriteImpl>::fromExisting(this));
}
}
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 1f113c045360..04ecb3895aa2 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -109,15 +109,19 @@ public:
*
* Clients are responsible for animating sprites by periodically updating their properties.
*/
-class SpriteController : public MessageHandler {
-protected:
- virtual ~SpriteController();
-
+class SpriteController {
public:
using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
+ SpriteController(const SpriteController&) = delete;
+ SpriteController& operator=(const SpriteController&) = delete;
+ virtual ~SpriteController();
- /* Creates a new sprite, initially invisible. */
+ /* Initialize the callback for the message handler. */
+ void setHandlerController(const std::shared_ptr<SpriteController>& controller);
+
+ /* Creates a new sprite, initially invisible. The lifecycle of the sprite must not extend beyond
+ * the lifecycle of this SpriteController. */
virtual sp<Sprite> createSprite();
/* Opens or closes a transaction to perform a batch of sprite updates as part of
@@ -129,9 +133,12 @@ public:
virtual void closeTransaction();
private:
- enum {
- MSG_UPDATE_SPRITES,
- MSG_DISPOSE_SURFACES,
+ class Handler : public virtual android::MessageHandler {
+ public:
+ enum { MSG_UPDATE_SPRITES, MSG_DISPOSE_SURFACES };
+
+ void handleMessage(const Message& message) override;
+ std::weak_ptr<SpriteController> spriteController;
};
enum {
@@ -192,7 +199,7 @@ private:
virtual ~SpriteImpl();
public:
- explicit SpriteImpl(const sp<SpriteController> controller);
+ explicit SpriteImpl(SpriteController& controller);
virtual void setIcon(const SpriteIcon& icon);
virtual void setVisible(bool visible);
@@ -220,7 +227,7 @@ private:
}
private:
- sp<SpriteController> mController;
+ SpriteController& mController;
struct Locked {
SpriteState state;
@@ -245,7 +252,7 @@ private:
sp<Looper> mLooper;
const int32_t mOverlayLayer;
- sp<WeakMessageHandler> mHandler;
+ sp<Handler> mHandler;
ParentSurfaceProvider mParentSurfaceProvider;
sp<SurfaceComposerClient> mSurfaceComposerClient;
@@ -260,7 +267,6 @@ private:
void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
- void handleMessage(const Message& message);
void doUpdateSprites();
void doDisposeSurfaces();
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index d9fe5996bcff..b8de919fbd8c 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -39,15 +39,15 @@ namespace android {
// --- Spot ---
-void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
+void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
int32_t displayId) {
sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
sprite->setAlpha(alpha);
sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
- sprite->setPosition(x, y);
+ sprite->setPosition(newX, newY);
sprite->setDisplayId(displayId);
- this->x = x;
- this->y = y;
+ x = newX;
+ y = newY;
if (icon != mLastIcon) {
mLastIcon = icon;
@@ -98,8 +98,8 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32
#endif
std::scoped_lock lock(mLock);
- sp<SpriteController> spriteController = mContext.getSpriteController();
- spriteController->openTransaction();
+ auto& spriteController = mContext.getSpriteController();
+ spriteController.openTransaction();
// Add or move spots for fingers that are down.
for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -125,7 +125,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32
}
}
- spriteController->closeTransaction();
+ spriteController.closeTransaction();
}
void TouchSpotController::clearSpots() {
@@ -167,7 +167,7 @@ TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t
sprite = mLocked.recycledSprites.back();
mLocked.recycledSprites.pop_back();
} else {
- sprite = mContext.getSpriteController()->createSprite();
+ sprite = mContext.getSpriteController().createSprite();
}
// Return the new spot.
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 4eabfb238f4a..8445032293dd 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -26,6 +26,21 @@ cc_test {
srcs: [
"PointerController_test.cpp",
],
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ },
shared_libs: [
"libandroid_runtime",
"libinputservice",
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 85747514aa03..d9efd3c2fd83 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -148,6 +148,25 @@ void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t dis
latestPointerDisplayId = displayId;
}
+class TestPointerController : public PointerController {
+public:
+ TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
+ sp<PointerControllerPolicyInterface> policy, const sp<Looper>& looper,
+ SpriteController& spriteController)
+ : PointerController(
+ policy, looper, spriteController,
+ /*enabled=*/true,
+ [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Register listener
+ registeredListener = listener;
+ },
+ [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+ // Unregister listener
+ if (registeredListener == listener) registeredListener = nullptr;
+ }) {}
+ ~TestPointerController() override {}
+};
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
@@ -157,8 +176,9 @@ protected:
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
- sp<MockSpriteController> mSpriteController;
+ std::unique_ptr<MockSpriteController> mSpriteController;
std::shared_ptr<PointerController> mPointerController;
+ sp<android::gui::WindowInfosListener> mRegisteredListener;
private:
void loopThread();
@@ -175,17 +195,18 @@ private:
PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
-
- mSpriteController = new NiceMock<MockSpriteController>(mLooper);
+ mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper));
mPolicy = new MockPointerControllerPolicyInterface();
EXPECT_CALL(*mSpriteController, createSprite())
.WillOnce(Return(mPointerSprite));
- mPointerController = PointerController::create(mPolicy, mLooper, mSpriteController);
+ mPointerController = std::make_unique<TestPointerController>(mRegisteredListener, mPolicy,
+ mLooper, *mSpriteController);
}
PointerControllerTest::~PointerControllerTest() {
+ mPointerController.reset();
mRunning.store(false, std::memory_order_relaxed);
mThread.join();
}
@@ -316,29 +337,16 @@ TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
class PointerControllerWindowInfoListenerTest : public Test {};
-class TestPointerController : public PointerController {
-public:
- TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
- const sp<Looper>& looper)
- : PointerController(
- new MockPointerControllerPolicyInterface(), looper,
- new NiceMock<MockSpriteController>(looper),
- [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Register listener
- registeredListener = listener;
- },
- [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
- // Unregister listener
- if (registeredListener == listener) registeredListener = nullptr;
- }) {}
-};
-
TEST_F(PointerControllerWindowInfoListenerTest,
doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) {
+ sp<Looper> looper = new Looper(false);
+ auto spriteController = NiceMock<MockSpriteController>(looper);
sp<android::gui::WindowInfosListener> registeredListener;
sp<android::gui::WindowInfosListener> localListenerCopy;
+ sp<MockPointerControllerPolicyInterface> policy = new MockPointerControllerPolicyInterface();
{
- TestPointerController pointerController(registeredListener, new Looper(false));
+ TestPointerController pointerController(registeredListener, policy, looper,
+ spriteController);
ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
localListenerCopy = registeredListener;
}