summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java39
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java61
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt44
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt98
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt27
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml14
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java)168
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt20
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt5
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt12
-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/bar/BubbleBarExpandedView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java128
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java262
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java162
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt56
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt52
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt51
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt74
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java191
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt173
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt146
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt129
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt126
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java74
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java178
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java270
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java331
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt)46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java189
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt102
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java207
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt99
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt8
-rw-r--r--libs/appfunctions/Android.bp8
-rw-r--r--libs/appfunctions/api/current.txt54
-rw-r--r--libs/appfunctions/appfunctions.extension.xml (renamed from libs/appfunctions/appfunctions.sidecar.xml)4
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java212
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java)25
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java)31
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java)6
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java103
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java)74
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java359
-rw-r--r--libs/appfunctions/tests/Android.bp2
-rw-r--r--libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt (renamed from libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt)78
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/FeatureFlags.h4
-rw-r--r--libs/hwui/Properties.cpp9
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig10
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp4
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp1
-rw-r--r--libs/hwui/hwui/MinikinUtils.h2
-rw-r--r--libs/hwui/hwui/Paint.h2
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp11
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp4
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp75
-rw-r--r--libs/hwui/jni/Graphics.cpp2
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp4
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp6
-rw-r--r--libs/hwui/libhwui.map.txt1
-rw-r--r--libs/hwui/utils/StatsUtils.cpp102
-rw-r--r--libs/hwui/utils/StatsUtils.h33
236 files changed, 5887 insertions, 1756 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 438532725686..76eb207a31c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -16,8 +16,10 @@
package androidx.window.extensions.area;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@@ -103,6 +105,30 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int mLastReportedRearDisplayPresentationStatus;
+ @VisibleForTesting
+ static int getRdmV1Identifier(List<DeviceState> currentSupportedDeviceStates) {
+ for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+ DeviceState state = currentSupportedDeviceStates.get(i);
+ if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)
+ && !state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+ return state.getIdentifier();
+ }
+ }
+ return INVALID_DEVICE_STATE_IDENTIFIER;
+ }
+
+ @VisibleForTesting
+ static int getRdmV2Identifier(List<DeviceState> currentSupportedDeviceStates) {
+ for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+ DeviceState state = currentSupportedDeviceStates.get(i);
+ if (state.hasProperties(PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+ return state.getIdentifier();
+ }
+ }
+ return INVALID_DEVICE_STATE_IDENTIFIER;
+ }
+
public WindowAreaComponentImpl(@NonNull Context context) {
mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
mDisplayManager = context.getSystemService(DisplayManager.class);
@@ -111,12 +137,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedDeviceStates();
if (Flags.deviceStatePropertyMigration()) {
- for (int i = 0; i < mCurrentSupportedDeviceStates.size(); i++) {
- DeviceState state = mCurrentSupportedDeviceStates.get(i);
- if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)) {
- mRearDisplayState = state.getIdentifier();
- break;
- }
+ if (Flags.deviceStateRdmV2()) {
+ mRearDisplayState = getRdmV2Identifier(mCurrentSupportedDeviceStates);
+ } else {
+ mRearDisplayState = getRdmV1Identifier(mCurrentSupportedDeviceStates);
}
} else {
mFoldedDeviceStates = context.getResources().getIntArray(
@@ -569,7 +593,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
private boolean isDeviceFolded() {
if (Flags.deviceStatePropertyApi()) {
return mCurrentDeviceState.hasProperty(
- PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
+ && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY);
} else {
return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
index ccb4ebe9199e..d677fef5c22c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
@@ -16,8 +16,13 @@
package androidx.window.extensions.area;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
import static org.junit.Assert.assertEquals;
+import android.hardware.devicestate.DeviceState;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.view.Surface;
@@ -29,11 +34,34 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WindowAreaComponentImplTests {
+ private static final DeviceState REAR_DISPLAY_STATE_V1 = new DeviceState(
+ new DeviceState.Configuration.Builder(1, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY))
+ .build());
+ private static final DeviceState REAR_DISPLAY_STATE_V2 = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+ .build());
+ // The PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT state must be present together with the
+ // PROPERTY_FEATURE_REAR_DISPLAY state in order to be a valid state.
+ private static final DeviceState INVALID_REAR_DISPLAY_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+ .build());
+
private final DisplayMetrics mTestDisplayMetrics = new DisplayMetrics();
@Before
@@ -93,4 +121,37 @@ public class WindowAreaComponentImplTests {
Surface.ROTATION_270, Surface.ROTATION_0, mTestDisplayMetrics);
assertEquals(expectedMetrics, mTestDisplayMetrics);
}
+
+ @Test
+ public void testRdmV1Identifier() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+ supportedStates.add(REAR_DISPLAY_STATE_V2);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+
+ supportedStates.add(REAR_DISPLAY_STATE_V1);
+ assertEquals(REAR_DISPLAY_STATE_V1.getIdentifier(),
+ WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+ }
+
+ @Test
+ public void testRdmV2Identifier_whenStateIsImproperlyConfigured() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+ supportedStates.add(INVALID_REAR_DISPLAY_STATE);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+ }
+
+ @Test
+ public void testRdmV2Identifier_whenStateIsProperlyConfigured() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+
+ supportedStates.add(REAR_DISPLAY_STATE_V1);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+
+ supportedStates.add(REAR_DISPLAY_STATE_V2);
+ assertEquals(REAR_DISPLAY_STATE_V2.getIdentifier(),
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+ }
}
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index b2ac640a468d..636e3cfd571d 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,6 @@
android:name=".desktopmode.DesktopWallpaperActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
- android:showForAllUsers="true"
android:theme="@style/DesktopWallpaperTheme" />
<activity
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 0b515f590f98..5f42bb161204 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -475,6 +475,6 @@ class BubbleStackViewTest {
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 08d647de4a51..6ac36a3319c9 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -47,6 +47,7 @@ import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import java.util.Collections
import java.util.concurrent.Executor
@@ -147,6 +148,7 @@ class BubbleBarExpandedViewTest {
@After
fun tearDown() {
testableRegionSamplingHelper?.stopAndDestroy()
+ getInstrumentation().waitForIdleSync()
}
@Test
@@ -218,8 +220,8 @@ class BubbleBarExpandedViewTest {
@Test
fun testEventLogging_dismissBubbleViaAppMenu() {
getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() }
- val dismissMenuItem =
- bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG)
+ val dismissMenuItem = bubbleExpandedView.menuView()
+ .actionViewWithText(context.getString(R.string.bubble_dismiss_text))
assertThat(dismissMenuItem).isNotNull()
getInstrumentation().runOnMainSync { dismissMenuItem.performClick() }
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
@@ -228,6 +230,42 @@ class BubbleBarExpandedViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @Test
+ fun testEventLogging_openAppSettings() {
+ getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() }
+ val appMenuItem = bubbleExpandedView.menuView()
+ .actionViewWithText(context.getString(R.string.bubbles_app_settings, bubble.appName))
+ getInstrumentation().runOnMainSync { appMenuItem.performClick() }
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_APP_MENU_GO_TO_SETTINGS.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ @Test
+ fun testEventLogging_unBubbleConversation() {
+ getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() }
+ val menuItem = bubbleExpandedView.menuView()
+ .actionViewWithText(context.getString(R.string.bubbles_dont_bubble_conversation))
+ getInstrumentation().runOnMainSync { menuItem.performClick() }
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_APP_MENU_OPT_OUT.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ private fun BubbleBarExpandedView.menuView(): BubbleBarMenuView {
+ return findViewByPredicate { it is BubbleBarMenuView }
+ }
+
+ private fun BubbleBarMenuView.actionViewWithText(text: CharSequence): View {
+ val views = ArrayList<View>()
+ findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT)
+ assertWithMessage("Expecting a single action with text '$text'").that(views).hasSize(1)
+ // findViewsWithText returns the TextView, but the click listener is on the parent container
+ return views.first().parent as View
+ }
+
private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
@@ -337,7 +375,7 @@ class BubbleBarExpandedViewTest {
override fun hideCurrentInputMethod() {
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 2fbf089d99d6..0044593ad228 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -19,12 +19,15 @@ package com.android.wm.shell.bubbles.bar
import android.app.ActivityManager
import android.content.Context
import android.content.pm.LauncherApps
+import android.graphics.PointF
import android.os.Handler
import android.os.UserManager
import android.view.IWindowManager
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
+import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -48,6 +51,7 @@ import com.android.wm.shell.bubbles.BubbleTaskViewFactory
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.FakeBubbleFactory
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
@@ -57,6 +61,7 @@ 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.shared.TransactionPool
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -66,8 +71,10 @@ import com.android.wm.shell.taskview.TaskViewTaskController
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import java.util.Collections
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -79,18 +86,28 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class BubbleBarLayerViewTest {
+ companion object {
+ @JvmField @ClassRule
+ val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+ }
+
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var bubbleBarLayerView: BubbleBarLayerView
private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var bubbleController: BubbleController
+
+ private lateinit var bubblePositioner: BubblePositioner
+
private lateinit var bubble: Bubble
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
ProtoLog.init()
+ PhysicsAnimatorTestUtils.prepareForTest()
uiEventLoggerFake = UiEventLoggerFake()
val bubbleLogger = BubbleLogger(uiEventLoggerFake)
@@ -100,7 +117,7 @@ class BubbleBarLayerViewTest {
val windowManager = context.getSystemService(WindowManager::class.java)
- val bubblePositioner = BubblePositioner(context, windowManager)
+ bubblePositioner = BubblePositioner(context, windowManager)
bubblePositioner.setShowingInBubbleBar(true)
val bubbleData =
@@ -113,7 +130,7 @@ class BubbleBarLayerViewTest {
bgExecutor,
)
- val bubbleController =
+ bubbleController =
createBubbleController(
bubbleData,
windowManager,
@@ -151,6 +168,12 @@ class BubbleBarLayerViewTest {
bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
}
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ getInstrumentation().waitForIdleSync()
+ }
+
private fun createBubbleController(
bubbleData: BubbleData,
windowManager: WindowManager?,
@@ -224,6 +247,70 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @Test
+ fun testEventLogging_dragExpandedViewLeft() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ // Drag from right to left
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge())
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge())
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge())
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ @Test
+ fun testEventLogging_dragExpandedViewRight() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ // Drag from left to right
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge())
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge())
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge())
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ private fun leftEdge(): PointF {
+ val screenSize = bubblePositioner.availableRect
+ return PointF(screenSize.left.toFloat(), screenSize.height() / 2f)
+ }
+
+ private fun rightEdge(): PointF {
+ val screenSize = bubblePositioner.availableRect
+ return PointF(screenSize.right.toFloat(), screenSize.height() / 2f)
+ }
+
+ private fun waitForExpandedViewAnimation() {
+ // wait for idle to allow the animation to start
+ getInstrumentation().waitForIdleSync()
+ getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) }
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+ }
+
private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) :
BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
@@ -264,7 +351,7 @@ class BubbleBarLayerViewTest {
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
@@ -290,4 +377,9 @@ class BubbleBarLayerViewTest {
}
}
}
+
+ private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) {
+ val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0)
+ getInstrumentation().runOnMainSync { dispatchTouchEvent(event) }
+ }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index ecb2b25a02f1..d4cbe6e10971 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest {
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
container = FrameLayout(context)
val windowManager = context.getSystemService(WindowManager::class.java)
positioner = BubblePositioner(context, windowManager)
@@ -85,7 +86,7 @@ class BubbleExpandedViewPinControllerTest {
isSmallTablet = false,
isLandscape = true,
isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
+ insets = Insets.of(10, 20, 30, 40),
)
positioner.update(deviceConfig)
positioner.bubbleBarTopOnScreen =
@@ -407,12 +408,26 @@ class BubbleExpandedViewPinControllerTest {
assertThat(testListener.locationReleases).containsExactly(RIGHT)
}
+ /** Send drag start event when on left */
+ @Test
+ fun start_onLeft_sendStartEventOnLeft() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) }
+ assertThat(testListener.locationStart).containsExactly(LEFT)
+ }
+
+ /** Send drag start event when on right */
+ @Test
+ fun start_onRight_sendStartEventOnRight() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) }
+ assertThat(testListener.locationStart).containsExactly(RIGHT)
+ }
+
private fun getExpectedDropTargetBoundsOnLeft(): Rect =
Rect().also {
positioner.getBubbleBarExpandedViewBounds(
true /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -421,7 +436,7 @@ class BubbleExpandedViewPinControllerTest {
positioner.getBubbleBarExpandedViewBounds(
false /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -446,8 +461,14 @@ class BubbleExpandedViewPinControllerTest {
}
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
+ val locationStart = mutableListOf<BubbleBarLocation>()
val locationChanges = mutableListOf<BubbleBarLocation>()
val locationReleases = mutableListOf<BubbleBarLocation>()
+
+ override fun onStart(location: BubbleBarLocation) {
+ locationStart.add(location)
+ }
+
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 501bedd50f55..c2755ef6ccb6 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -19,6 +19,7 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
+ android:clipChildren="false"
android:id="@+id/bubble_expanded_view">
<com.android.wm.shell.bubbles.bar.BubbleBarHandleView
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index f1ecde49ce78..7aca921dccc7 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -14,20 +14,18 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
+ android:clipToPadding="false"
android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
android:orientation="vertical"
- android:elevation="@dimen/bubble_manage_menu_elevation"
- android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
- android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
- android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
- android:clipToPadding="false">
+ android:visibility="invisible"
+ tools:visibility="visible">
<LinearLayout
android:id="@+id/bubble_bar_manage_menu_bubble_section"
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
index f90e165ffc74..a18a2510f0f7 100644
--- 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
@@ -168,7 +168,7 @@
</LinearLayout>
<LinearLayout
- android:id="@+id/open_in_browser_pill"
+ android:id="@+id/open_in_app_or_browser_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -178,7 +178,7 @@
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
- android:id="@+id/open_in_browser_button"
+ android:id="@+id/open_in_app_or_browser_button"
android:layout_weight="1"
android:contentDescription="@string/open_in_browser_text"
android:text="@string/open_in_browser_text"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 95c2bb59d202..a4aa3480fb46 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Maak in blaaier oop"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nuwe venster"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Bestuur vensters"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Verander aspekverhouding"</string>
<string name="close_text" msgid="4986518933445178928">"Maak toe"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gryp skerm vas"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Meesleurend"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Stel terug"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeer"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Stel terug"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Spring na links"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In die app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In jou blaaier"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Maak hier apps vinnig in jou blaaier oop"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index ba74e342f353..1cd980460cee 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"በአሳሽ ውስጥ ክፈት"</string>
<string name="new_window_text" msgid="6318648868380652280">"አዲስ መስኮት"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"መስኮቶችን አስተዳድር"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ምጥጥነ ገፅታ ለውጥ"</string>
<string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ማያ ገጹን አሳድግ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"አስማጭ"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ወደነበረበት መልስ"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"አሳድግ"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ወደነበረበት መልስ"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ወደ ግራ አሳድግ"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"በመተግበሪያው ውስጥ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"በአሳሽዎ ውስጥ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"እሺ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"እዚህ አሳሽዎ ውስጥ መተግበሪያዎችን በፍጥነት ይክፈቱ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index a8febc80ffc1..41ebfcd0ee85 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"فتح في المتصفِّح"</string>
<string name="new_window_text" msgid="6318648868380652280">"نافذة جديدة"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"إدارة النوافذ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغيير نسبة العرض إلى الارتفاع"</string>
<string name="close_text" msgid="4986518933445178928">"إغلاق"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"في التطبيق"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"في المتصفِّح"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"حسنًا"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"فتح التطبيقات بسرعة في المتصفِّح هنا"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 8c924e342875..203fed0aecef 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ব্ৰাউজাৰত খোলক"</string>
<string name="new_window_text" msgid="6318648868380652280">"নতুন ৱিণ্ড’"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ৱিণ্ড’ পৰিচালনা কৰক"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"আকাৰৰ অনুপাত সলনি কৰক"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"এপ্‌টোত"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"আপোনাৰ ব্ৰাউজাৰত"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ঠিক আছে"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ইয়াত আপোনাৰ ব্ৰাউজাৰত ক্ষিপ্ৰভাৱে এপ্ খোলক"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index aa232e330604..31ddc9b78b68 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Brauzerdə açın"</string>
<string name="new_window_text" msgid="6318648868380652280">"Yeni pəncərə"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Pəncərələri idarə edin"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tərəflər nisbətini dəyişin"</string>
<string name="close_text" msgid="4986518933445178928">"Bağlayın"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranı çəkin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"İmmersiv"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Bərpa edin"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Böyüdün"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Bərpa edin"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tərəf çəkin"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Tətbiqdə"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauzerinizdə"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Brauzerinizdəki tətbiqləri burada sürətlə açın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 256344a4cb31..486b3cfbfee4 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otvorite u pregledaču"</string>
<string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Upravljajte prozorima"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promenite razmeru"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Uklopi ekran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imerzivne"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Uvećajte"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vratite"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prikačite levo"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledaču"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Potvrdi"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovde možete brzo da otvarate aplikacije u pregledaču"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 701c51091aa4..cc42da947c36 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Адкрыць у браўзеры"</string>
<string name="new_window_text" msgid="6318648868380652280">"Новае акно"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Кіраваць вокнамі"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змяніць суадносіны бакоў"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыць"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Размясціць на палавіне экрана"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"З эфектам прысутнасці"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Аднавіць"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Разгарнуць"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Аднавіць"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Размясціць злева"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У праграме"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У браўзеры"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ОК"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Тут можна хутка адкрываць праграмы ў браўзеры"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 9ab86f4cbc56..c12b37b34d5a 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Отваряне в браузър"</string>
<string name="new_window_text" msgid="6318648868380652280">"Нов прозорец"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Управление на прозорците"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промяна на съотношението"</string>
<string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Прилепване на екрана"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Възстановяване"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увеличаване"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Възстановяване"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прилепване наляво"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложението"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"В браузъра ви"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Бързо отваряйте приложения в браузъра си оттук"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 22a445f1754c..aca5b34ae4c0 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ব্রাউজারে খুলুন"</string>
<string name="new_window_text" msgid="6318648868380652280">"নতুন উইন্ডো"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"উইন্ডো ম্যানেজ করুন"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"অ্যাপের মধ্যে"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"আপনার ব্রাউজারে"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ঠিক আছে"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"এখানে আপনার ব্রাউজারে দ্রুত অ্যাপ খুলুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 73f30d797883..6bd6473a5f13 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -127,14 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otvaranje u pregledniku"</string>
<string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promjena formata slike"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
- <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
- <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uvjerljivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vraćanje"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziranje"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vraćanje"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pomicanje ulijevo"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledniku"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Uredu"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovdje možete brzo otvarati aplikacije u pregledniku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 499ed329e511..d9ad5a68d163 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Obre al navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Finestra nova"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gestiona les finestres"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Canvia la relació d\'aspecte"</string>
<string name="close_text" msgid="4986518933445178928">"Tanca"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajusta la pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiu"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaura"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximitza"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaura"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajusta a l\'esquerra"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"A l\'aplicació"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Al navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"D\'acord"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Obre ràpidament aplicacions al navegador aquí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 6a5780e01822..ab51b666cdda 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otevřít v prohlížeči"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nové okno"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Spravovat okna"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Změnit poměr stran"</string>
<string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaci"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V prohlížeči"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Odtud můžete v prohlížeči rychle otevírat aplikace"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 430cf96cd72f..443620804e10 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Åbn i browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nyt vindue"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduer"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Skift billedformat"</string>
<string name="close_text" msgid="4986518933445178928">"Luk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tilpas skærm"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Opslugende"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gendan"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimér"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Gendan"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fastgør til venstre"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I din browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Åbn hurtigt apps i din browser her"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index cafaa89f57e3..b6e89c0eeb8e 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Im Browser öffnen"</string>
<string name="new_window_text" msgid="6318648868380652280">"Neues Fenster"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Fenster verwalten"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Seitenverhältnis ändern"</string>
<string name="close_text" msgid="4986518933445178928">"Schließen"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Bildschirm teilen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Wiederherstellen"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximieren"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Wiederherstellen"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links andocken"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In der App"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In deinem Browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ok"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Hier kannst du Apps schnell in deinem Browser öffnen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index d02fae2a986d..601c0ceee27f 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Άνοιγμα σε πρόγραμμα περιήγησης"</string>
<string name="new_window_text" msgid="6318648868380652280">"Νέο παράθυρο"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Διαχείριση παραθύρων"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Αλλαγή λόγου διαστάσεων"</string>
<string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Στην εφαρμογή"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Στο πρόγραμμα περιήγησής σας"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ΟΚ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ανοίξτε γρήγορα εφαρμογές στο πρόγραμμα περιήγησής σας εδώ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index f9911451f4b5..fd6317530109 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"New window"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 2d123ec3a3d4..dac1b9a1460d 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"New Window"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Manage Windows"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index f9911451f4b5..fd6317530109 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"New window"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index f9911451f4b5..fd6317530109 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Open in browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"New window"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Change aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In your browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Quickly open apps in your browser here"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 210b708b49af..e67fc8e2c63c 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir en el navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nueva ventana"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Administrar ventanas"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restablecer"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar a la izquierda"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"En un navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rápidamente apps en tu navegador aquí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 3c7bfe5a3cb4..2f5ec64be629 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir en el navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Ventana nueva"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gestionar ventanas"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar relación de aspecto"</string>
<string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Acoplar a la izquierda"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la aplicación"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"En el navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rápidamente aplicaciones en tu navegador aquí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index d17ee02a3a7f..dd78628093d4 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Avamine brauseris"</string>
<string name="new_window_text" msgid="6318648868380652280">"Uus aken"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Akende haldamine"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Kuvasuhte muutmine"</string>
<string name="close_text" msgid="4986518933445178928">"Sule"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Kuva poolel ekraanil"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Kaasahaarav"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Taasta"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeeri"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Taasta"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Tõmmake vasakule"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Rakenduses"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauseris"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Avage rakendusi kiiresti oma brauseris siin"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index f9419bc4614b..1cfc69457ce9 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Ireki arakatzailean"</string>
<string name="new_window_text" msgid="6318648868380652280">"Leiho berria"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Kudeatu leihoak"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Aldatu aspektu-erlazioa"</string>
<string name="close_text" msgid="4986518933445178928">"Itxi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zatitu pantaila"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Murgiltzailea"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Leheneratu"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizatu"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Leheneratu"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ezarri ezkerrean"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Aplikazioan"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Arakatzailean"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ados"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ireki aplikazioak arakatzailean bizkor"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index a3d3cbc872fd..f76f67d2e796 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"باز کردن در مرورگر"</string>
<string name="new_window_text" msgid="6318648868380652280">"پنجره جدید"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"مدیریت کردن پنجره‌ها"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تغییر نسبت ابعادی"</string>
<string name="close_text" msgid="4986518933445178928">"بستن"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"بزرگ کردن صفحه"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمی‌توان به اینجا منتقل کرد"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"فراگیر"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بازیابی"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بزرگ کردن"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"بازیابی کردن"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"کشیدن به‌چپ"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"در برنامه"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"در مرورگر"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"تأیید"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"در اینجا سریع برنامه‌ها را در مرورگرتان باز کنید"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index ee5dd6516098..a1ec0150ffae 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Avaa selaimessa"</string>
<string name="new_window_text" msgid="6318648868380652280">"Uusi ikkuna"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Hallinnoi ikkunoita"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Vaihda kuvasuhdetta"</string>
<string name="close_text" msgid="4986518933445178928">"Sulje"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Jaa näyttö"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiivinen"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Palauta"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Suurenna"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Palauta"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Siirrä vasemmalle"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sovelluksessa"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Selaimella"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Avaa sovellukset nopeasti selaimessa täältä"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index dc4789169146..1b9b74a45671 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Ouvrir dans le navigateur"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nouvelle fenêtre"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier les proportions"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aligner l\'écran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurer"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Épingler à gauche"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'appli"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Dans votre navigateur"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ouvrez rapidement des applis dans votre navigateur ici"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index a52ab49da3ab..7e0a0b1acb7b 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Ouvrir dans un navigateur"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nouvelle fenêtre"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Modifier le format"</string>
<string name="close_text" msgid="4986518933445178928">"Fermer"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fractionner l\'écran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurer"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ancrer à gauche"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'application"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Dans votre navigateur"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ouvrez rapidement des applications dans votre navigateur ici"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 97d5e51e5b98..bdd07476efcf 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Ventá nova"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Xestionar as ventás"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambiar a proporción"</string>
<string name="close_text" msgid="4986518933445178928">"Pechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Axustar á esquerda"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na aplicación"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Aceptar"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abre rapidamente aplicacións no navegador aquí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 362ff8d874bb..d23c4fd8f0e0 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"બ્રાઉઝરમાં ખોલો"</string>
<string name="new_window_text" msgid="6318648868380652280">"નવી વિન્ડો"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"વિન્ડો મેનેજ કરો"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"સાપેક્ષ ગુણોત્તર બદલો"</string>
<string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"સ્ક્રીન સ્નૅપ કરો"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ઇમર્સિવ"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"રિસ્ટોર કરો"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"મોટું કરો"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"રિસ્ટોર કરો"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ડાબે સ્નૅપ કરો"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ઍપમાં"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"તમારા બ્રાઉઝરમાં"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ઓકે"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"તમારા બ્રાઉઝરમાં અહીં ઝડપથી ઍપ ખોલો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 527793eac9c3..4eec6f877fab 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउज़र में खोलें"</string>
<string name="new_window_text" msgid="6318648868380652280">"नई विंडो"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"विंडो मैनेज करें"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ऐप्लिकेशन में"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"आपके ब्राउज़र में"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ठीक है"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"अपने ब्राउज़र में तुरंत ऐप्लिकेशन खोलें"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 659d1ec39b73..a119d9e7f782 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otvori u pregledniku"</string>
<string name="new_window_text" msgid="6318648868380652280">"Novi prozor"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Promijeni omjer slike"</string>
<string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"U pregledniku"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"U redu"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ovdje brzo otvorite aplikacije u pregledniku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 943b5eb30768..c07b6c3b9f1d 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Megnyitás böngészőben"</string>
<string name="new_window_text" msgid="6318648868380652280">"Új ablak"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Ablakok kezelése"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Méretarány módosítása"</string>
<string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Igazodás a képernyő adott részéhez"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Magával ragadó"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Visszaállítás"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Teljes méret"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Visszaállítás"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Balra igazítás"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Az alkalmazásban"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"A böngészőben"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Itt gyorsan megnyithatja az alkalmazásokat a böngészőben"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 6bcfc9a22d6e..52eb18580de1 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -92,8 +92,8 @@
<string name="bubble_shortcut_label" msgid="666269077944378311">"Ամպիկներ"</string>
<string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Ցույց տալ ամպիկներ"</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="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>
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Բացել դիտարկիչում"</string>
<string name="new_window_text" msgid="6318648868380652280">"Նոր պատուհան"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Կառավարել պատուհանները"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Փոխել կողմերի հարաբերակցությունը"</string>
<string name="close_text" msgid="4986518933445178928">"Փակել"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ծալել էկրանը"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Ներկայության էֆեկտով"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Վերականգնել"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ծավալել"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Վերականգնել"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ամրացնել ձախ կողմում"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Հավելվածում"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Ձեր դիտարկիչում"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Եղավ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Արագ բացեք հավելվածները ձեր դիտարկիչում"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 96a3ebce9a45..f8f9d5e16439 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Buka di browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Jendela Baru"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Kelola Jendela"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ubah rasio aspek"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gabungkan Layar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersif"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimalkan"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Pulihkan"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Maksimalkan ke kiri"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Di aplikasi"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Di browser Anda"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Oke"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buka aplikasi di browser Anda dengan cepat di sini"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index ca1bc15edf33..8a9e3c0ca0a4 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Opna í vafra"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nýr gluggi"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Stjórna gluggum"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Breyta myndhlutfalli"</string>
<string name="close_text" msgid="4986518933445178928">"Loka"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Í forritinu"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Í vafranum"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Í lagi"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Opna forrit fljótt í vafranum þínum hér"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 87919b5dc1ff..138adefed160 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Apri nel browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nuova finestra"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gestisci finestre"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Cambia proporzioni"</string>
<string name="close_text" msgid="4986518933445178928">"Chiudi"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"All\'interno dell\'app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Nel browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Ok"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Apri rapidamente le app nel browser qui"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 17ffe8ee7600..917738dc1575 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -109,7 +109,7 @@
<string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
- <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין להציג שוב"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"לא להציג שוב"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
<string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"פתיחה בדפדפן"</string>
<string name="new_window_text" msgid="6318648868380652280">"חלון חדש"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ניהול החלונות"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"שינוי של יחס גובה-רוחב"</string>
<string name="close_text" msgid="4986518933445178928">"סגירה"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"כיווץ המסך"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"סוחף"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"שחזור"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"הגדלה"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"שחזור"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"הצמדה לשמאל"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"באפליקציה"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"בדפדפן"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"אישור"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"כאן אפשר לפתוח אפליקציות בדפדפן במהירות"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index c7a77d9b9214..35c48212cf39 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ブラウザで開く"</string>
<string name="new_window_text" msgid="6318648868380652280">"新しいウィンドウ"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ウィンドウを管理する"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"アスペクト比を変更"</string>
<string name="close_text" msgid="4986518933445178928">"閉じる"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"アプリ内"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ブラウザ内"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ブラウザでアプリをすばやく開けます"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 39362ef4ca57..9b9966f152ee 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ბრაუზერში გახსნა"</string>
<string name="new_window_text" msgid="6318648868380652280">"ახალი ფანჯარა"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ფანჯრების მართვა"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"თანაფარდობის შეცვლა"</string>
<string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"აპში"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"თქვენს ბრაუზერში"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"კარგი"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"სწრაფად გახსენით აპები თქვენს ბრაუზერში აქ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 45f85b9e9a70..8618ba9b2b0f 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Браузерден ашу"</string>
<string name="new_window_text" msgid="6318648868380652280">"Жаңа терезе"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Терезелерді басқару"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Арақатынасты өзгерту"</string>
<string name="close_text" msgid="4986518933445178928">"Жабу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды бөлу"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Әсерлі"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Қалпына келтіру"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Жаю"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Қалпына келтіру"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солға тіркеу"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Қолданбада"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Браузерде"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Жарайды"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Осындағы браузерде қолданбаларды жылдам ашуға болады."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 9c4ae05f3a36..7f853f3e1e2f 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"បើកក្នុងកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
<string name="new_window_text" msgid="6318648868380652280">"វិនដូ​ថ្មី"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"គ្រប់គ្រង​វិនដូ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ប្ដូរ​​សមាមាត្រ"</string>
<string name="close_text" msgid="4986518933445178928">"បិទ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"នៅក្នុងកម្មវិធី"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"នៅក្នុង​កម្មវិធីរុករកតាម​អ៊ីនធឺណិត​របស់អ្នក"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"យល់ព្រម"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"បើកកម្មវិធីយ៉ាងរហ័សនៅក្នុងកម្មវិធីរុករកតាមអ៊ីនធឺណិតរបស់អ្នកនៅទីនេះ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index f365cfb34412..456dea2fdb0f 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ಬ್ರೌಸರ್‌ನಲ್ಲಿ ತೆರೆಯಿರಿ"</string>
<string name="new_window_text" msgid="6318648868380652280">"ಹೊಸ ವಿಂಡೋ"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ವಿಂಡೋಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ಆ್ಯಪ್‌ನಲ್ಲಿ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ನಿಮ್ಮ ಬ್ರೌಸರ್‌ನಲ್ಲಿ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ಸರಿ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ಇಲ್ಲಿಂದ ನಿಮ್ಮ ಬ್ರೌಸರ್‌ನಲ್ಲಿ ಆ್ಯಪ್‌ಗಳನ್ನು ತ್ವರಿತವಾಗಿ ತೆರೆಯಿರಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 2bf1b05a919b..763cda738541 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"브라우저에서 열기"</string>
<string name="new_window_text" msgid="6318648868380652280">"새 창"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"창 관리"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"가로세로 비율 변경"</string>
<string name="close_text" msgid="4986518933445178928">"닫기"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"화면 분할"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"몰입형"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"복원"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"최대화하기"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"복원"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"왼쪽으로 맞추기"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"앱에서"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"브라우저에서"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"확인"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"이 브라우저에서 앱을 빠르게 여세요."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 392ae4cab107..bffc3b1d11a8 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Серепчиден ачуу"</string>
<string name="new_window_text" msgid="6318648868380652280">"Жаңы терезе"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Терезелерди тескөө"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Тараптардын катнашын өзгөртүү"</string>
<string name="close_text" msgid="4986518933445178928">"Жабуу"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды сүрөткө тартып алуу"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Сүңгүтүүчү"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Калыбына келтирүү"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Чоңойтуу"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Калыбына келтирүү"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солго жылдыруу"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Колдонмодо"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Серепчиңизде"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Жарайт"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Бул жерде серепчиңизден колдонмолорду тез ачасыз"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 4e4b678755b2..b48b07067521 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ເປີດໃນໂປຣແກຣມທ່ອງເວັບ"</string>
<string name="new_window_text" msgid="6318648868380652280">"ໜ້າຈໍໃໝ່"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ຈັດການໜ້າຈໍ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ປ່ຽນອັດຕາສ່ວນຮູບ"</string>
<string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ສະແນັບໜ້າຈໍ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ສົມຈິງ"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ກູ້ຄືນ"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ກູ້ຄືນ"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ແນບຊ້າຍ"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ໃນແອັບ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ໃນໂປຣແກຣມທ່ອງເວັບຂອງທ່ານ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ຕົກລົງ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ເປີດແອັບຢ່າງວ່ອງໄວໃນໂປຣແກຣມທ່ອງເວັບຂອງທ່ານບ່ອນນີ້"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 5a7f58e5781a..d7a907cdc105 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Atidaryti naršyklėje"</string>
<string name="new_window_text" msgid="6318648868380652280">"Naujas langas"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Tvarkyti langus"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Keisti kraštinių santykį"</string>
<string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Programoje"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Naršyklėje"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Gerai"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Čia greitai atidarykite programas naršyklėje"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 60912f627841..4ba7c2346a33 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Atvērt pārlūkā"</string>
<string name="new_window_text" msgid="6318648868380652280">"Jauns logs"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Pārvaldīt logus"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mainīt malu attiecību"</string>
<string name="close_text" msgid="4986518933445178928">"Aizvērt"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fiksēt ekrānu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Iekļaujoši"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atjaunot"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizēt"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Atjaunot"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Piestiprināt pa kreisi"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Lietotnē"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Pārlūkprogrammā"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Labi"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"No šejienes varat ātri atvērt lietotnes savā pārlūkprogrammā"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 7c0c856ed1a7..d20eba55eac9 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Отвори во прелистувач"</string>
<string name="new_window_text" msgid="6318648868380652280">"Нов прозорец"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Управувајте со прозорци"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промени го соодносот"</string>
<string name="close_text" msgid="4986518933445178928">"Затворете"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Подели го екранот на половина"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Максимизирај"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Врати"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Фотографирај лево"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Во апликацијата"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Во прелистувачот"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Во ред"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Брзо отворајте ги апликациите во вашиот прелистувач овде"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index e14ab8b0161c..81c5094526c1 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ബ്രൗസറിൽ തുറക്കുക"</string>
<string name="new_window_text" msgid="6318648868380652280">"പുതിയ വിന്‍ഡോ"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"വിൻഡോകൾ മാനേജ് ചെയ്യുക"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"വീക്ഷണ അനുപാതം മാറ്റുക"</string>
<string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്‌ക്രീൻ വലുതാക്കുക"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"സ്‌ക്രീൻ സ്‌നാപ്പ് ചെയ്യുക"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ഇമേഴ്‌സീവ്"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"പുനഃസ്ഥാപിക്കുക"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"വലുതാക്കുക"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"പുനഃസ്ഥാപിക്കുക"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ഇടതുവശത്തേക്ക് സ്‌നാപ്പ് ചെയ്യുക"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ആപ്പിൽ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"നിങ്ങളുടെ ബ്രൗസറിൽ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ശരി"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"നിങ്ങളുടെ ബ്രൗസറിലെ ആപ്പുകൾ ഇവിടെ അതിവേഗം തുറക്കുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index d406b99e80b3..35da93e774b5 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Хөтчид нээх"</string>
<string name="new_window_text" msgid="6318648868380652280">"Шинэ цонх"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Windows-г удирдах"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Харьцааг өөрчлөх"</string>
<string name="close_text" msgid="4986518933445178928">"Хаах"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Дэлгэцийг таллах"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Бодит мэт"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Сэргээх"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Томруулах"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Сэргээх"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Зүүн тийш зэрэгцүүлэх"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Аппад"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Хөтчидөө"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Эндээс хөтчидөө аппуудыг шуурхай нээгээрэй"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 871bc3fcc8e7..c6b874a6780b 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउझरमध्ये उघडा"</string>
<string name="new_window_text" msgid="6318648868380652280">"नवीन विंडो"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"विंडो व्यवस्थापित करा"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"आस्पेक्ट रेशो बदला"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करा"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ॲपमध्ये"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"तुमच्या ब्राउझरमध्ये"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ओके"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"इथे तुमच्या ब्राउझरमध्ये अ‍ॅप्स झटपट उघडा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 71666cae93c8..0fce0e9da45f 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Buka dalam penyemak imbas"</string>
<string name="new_window_text" msgid="6318648868380652280">"Tetingkap Baharu"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Urus Tetingkap"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tukar nisbah bidang"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Pada apl"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Pada penyemak imbas"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buka apl dengan pantas dalam penyemak imbas anda di sini"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index ae34624c98a0..abc2a19ba989 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ဘရောင်ဇာတွင် ဖွင့်ရန်"</string>
<string name="new_window_text" msgid="6318648868380652280">"ဝင်းဒိုးအသစ်"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ဝင်းဒိုးများ စီမံရန်"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"အချိုးအစား ပြောင်းရန်"</string>
<string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"စခရင်ကို ချုံ့မည်"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"သုံးဘက်မြင်"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ပြန်ပြောင်းရန်"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ချဲ့ရန်"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ပြန်ပြောင်းရန်"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ဘယ်တွင် ချဲ့ရန်"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"အက်ပ်တွင်"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"သင်၏ဘရောင်ဇာတွင်"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"အက်ပ်များကို သင့်ဘရောင်ဇာတွင် ဤနေရာ၌ အမြန်ဖွင့်နိုင်သည်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 9270dc859728..ed6fb900564f 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Åpne i nettleseren"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nytt vindu"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduene"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Endre høyde/bredde-forholdet"</string>
<string name="close_text" msgid="4986518933445178928">"Lukk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fest skjermen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Oppslukende"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gjenopprett"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimer"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Gjenopprett"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fest til venstre"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I nettleseren"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Her kan du raskt åpne apper i nettleseren"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 7015b2c11b32..aff712901ff6 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ब्राउजरमा खोल्नुहोस्"</string>
<string name="new_window_text" msgid="6318648868380652280">"नयाँ विन्डो"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"विन्डोहरू व्यवस्थापन गर्नुहोस्"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"</string>
<string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"एपमा"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"तपाईंको ब्राउजरमा"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ठिक छ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"आफ्नो ब्राउजरबाट यहाँ तुरुन्तै एपहरू खोल्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 45305d62a69b..8db3a0ecfc2f 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Openen in browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nieuw venster"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Vensters beheren"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Beeldverhouding wijzigen"</string>
<string name="close_text" msgid="4986518933445178928">"Sluiten"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Scherm halveren"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersief"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Herstellen"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximaliseren"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Herstellen"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links uitlijnen"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In de app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"In je browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Open hier snel apps in je browser"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 2d30441ab6f4..69540898161c 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ବ୍ରାଉଜରରେ ଖୋଲନ୍ତୁ"</string>
<string name="new_window_text" msgid="6318648868380652280">"ନୂଆ ୱିଣ୍ଡୋ"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ୱିଣ୍ଡୋଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ସ୍କ୍ରିନକୁ ସ୍ନାପ କରନ୍ତୁ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ଇମର୍ସିଭ"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ବଡ଼ କରନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ବାମରେ ସ୍ନାପ କରନ୍ତୁ"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ଆପରେ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ଆପଣଙ୍କ ବ୍ରାଉଜରରେ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ଠିକ ଅଛି"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ଏଠାରେ ଆପଣଙ୍କ ବ୍ରାଉଜରରେ ଥିବା ଆପ୍ସକୁ ଶୀଘ୍ର ଖୋଲନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 26ba461cba5d..c627d7fcc2c5 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ ਖੋਲ੍ਹੋ"</string>
<string name="new_window_text" msgid="6318648868380652280">"ਨਵੀਂ ਵਿੰਡੋ"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"</string>
<string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ਸਕ੍ਰੀਨ ਨੂੰ ਸਨੈਪ ਕਰੋ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ਇਮਰਸਿਵ"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ਵੱਡਾ ਕਰੋ"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ਖੱਬੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ਐਪ ਵਿੱਚ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ਤੁਹਾਡੇ ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ਠੀਕ ਹੈ"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ਇੱਥੇ ਆਪਣੇ ਬ੍ਰਾਊਜ਼ਰ ਵਿੱਚ ਤੇਜ਼ੀ ਨਾਲ ਐਪਾਂ ਖੋਲ੍ਹੋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 5f78b134ad22..a138c08d319a 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otwórz w przeglądarce"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nowe okno"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Zarządzaj oknami"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmień format obrazu"</string>
<string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"W aplikacji"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"W przeglądarce"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tutaj możesz szybko otwierać aplikacje w przeglądarce"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 8c7f9e73296d..9942f6980306 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -52,10 +52,10 @@
<string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
<string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo uma mão"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo uma mão"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo uma mão"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo para uma mão"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configurações de balões do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu flutuante"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Devolver à pilha"</string>
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra apps no navegador rapidamente aqui"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index cd78ef95d88e..559eea2d3cab 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Faça a gestão das janelas"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Alterar formato"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra rapidamente apps no navegador aqui"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 8c7f9e73296d..9942f6980306 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -52,10 +52,10 @@
<string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
<string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo uma mão"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo uma mão"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo uma mão"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Sair do modo para uma mão"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configurações de balões do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu flutuante"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Devolver à pilha"</string>
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Abrir no navegador"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nova janela"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Mudar a proporção"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"No navegador"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Abra apps no navegador rapidamente aqui"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index e3fe2804bdcd..df0ee45695b2 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Deschide în browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Fereastră nouă"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Gestionează ferestrele"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Schimbă raportul de dimensiuni"</string>
<string name="close_text" msgid="4986518933445178928">"Închide"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Micșorează fereastra și fixeaz-o"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Captivant"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restabilește"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizează"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restabilește"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Trage la stânga"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"În aplicație"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"În browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Deschide rapid aplicații în browser aici"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 442fca3ef0a7..430f1b1448da 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Открыть в браузере"</string>
<string name="new_window_text" msgid="6318648868380652280">"Новое окно"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Управление окнами"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Изменить соотношение сторон"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Свернуть"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Погружение"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Восстановить"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Развернуть"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Восстановить"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Привязать слева"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложении"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"В браузере"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ОК"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Здесь можно быстро открывать приложения в браузере"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 8a7ad3b9f80c..3e3766768a7f 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"බ්‍රව්සරයේ විවෘත කරන්න"</string>
<string name="new_window_text" msgid="6318648868380652280">"නව කවුළුව"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"කවුළු කළමනාකරණය කරන්න"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"දර්ශන අනුපාතය වෙනස් කරන්න"</string>
<string name="close_text" msgid="4986518933445178928">"වසන්න"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ස්නැප් තිරය"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ගිලෙන සුළු"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ප්‍රතිසාධනය කරන්න"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"විහිදන්න"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ප්‍රතිසාධනය කරන්න"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"වමට ස්නැප් කරන්න"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"යෙදුම තුළ"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ඔබේ බ්‍රව්සරය තුළ"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"හරි"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"ඔබේ බ්‍රව්සරයේ යෙදුම් ඉක්මනින් විවෘත කරන්න"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4234e8073bc8..56a7edd08b23 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Otvoriť v prehliadači"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nové okno"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Správa okien"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Zmeniť pomer strán"</string>
<string name="close_text" msgid="4986518933445178928">"Zavrieť"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zobraziť polovicu obrazovky"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pútavé"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnoviť"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovať"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnoviť"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prichytiť vľavo"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikácii"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V prehliadači"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tu môžete rýchlo otvárať aplikácie v prehliadači"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index ae7e524da6cc..b6344c981fc9 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Odpri v brskalniku"</string>
<string name="new_window_text" msgid="6318648868380652280">"Novo okno"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje oken"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Sprememba razmerja stranic"</string>
<string name="close_text" msgid="4986518933445178928">"Zapri"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaciji"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"V brskalniku"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"V redu"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Tukaj hitro odprete aplikacije v brskalniku"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index de6f681cfe74..ab7499d505f8 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Hape në shfletues"</string>
<string name="new_window_text" msgid="6318648868380652280">"Dritare e re"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Menaxho dritaret"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ndrysho raportin e pamjes"</string>
<string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Regjistro ekranin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Përfshirës"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restauro"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizo"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restauro"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Zhvendos majtas"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Në aplikacion"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Në shfletuesin tënd"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Në rregull"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Hapi me shpejtësi aplikacionet në shfletues këtu"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 901d6d967a7d..773ed16dc4b9 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Отворите у прегледачу"</string>
<string name="new_window_text" msgid="6318648868380652280">"Нови прозор"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Управљајте прозорима"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Промените размеру"</string>
<string name="close_text" msgid="4986518933445178928">"Затворите"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Уклопи екран"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Имерзивне"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увећајте"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Вратите"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прикачите лево"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У апликацији"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У прегледачу"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Потврди"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Овде можете брзо да отварате апликације у прегледачу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 6566801b7c63..6f6a97b4495f 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Öppna i webbläsaren"</string>
<string name="new_window_text" msgid="6318648868380652280">"Nytt fönster"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Hantera fönster"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ändra bildformat"</string>
<string name="close_text" msgid="4986518933445178928">"Stäng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fäst skärmen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uppslukande"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Återställ"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Utöka"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Återställ"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fäst till vänster"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"I webbläsaren"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Öppna snabbt appar i webbläsaren här"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index a952011385de..72b7384fa83a 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Fungua katika kivinjari"</string>
<string name="new_window_text" msgid="6318648868380652280">"Dirisha Jipya"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Dhibiti Windows"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Badilisha uwiano"</string>
<string name="close_text" msgid="4986518933445178928">"Funga"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Panga Madirisha kwenye Skrini"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Shirikishi"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Rejesha"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Panua"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Rejesha"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Telezesha kushoto"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Kwenye programu"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Kwenye kivinjari chako"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Sawa"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Fungua programu kwa haraka katika kivinjari chako hapa"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 2c73d3a14620..9d902912b377 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"உலாவியில் திறக்கும்"</string>
<string name="new_window_text" msgid="6318648868380652280">"புதிய சாளரம்"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"சாளரங்களை நிர்வகிக்கலாம்"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"தோற்ற விகிதத்தை மாற்று"</string>
<string name="close_text" msgid="4986518933445178928">"மூடும்"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ஆப்ஸில்"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"உங்கள் பிரவுசரில்"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"சரி"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"உங்கள் உலாவியில் ஆப்ஸை இங்கே விரைவாகத் திறக்கலாம்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index b17d4d1afaf7..3c7c06a4fc1a 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"బ్రౌజర్‌లో తెరవండి"</string>
<string name="new_window_text" msgid="6318648868380652280">"కొత్త విండో"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"విండోలను మేనేజ్ చేయండి"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"ఆకార నిష్పత్తిని మార్చండి"</string>
<string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"యాప్‌లో"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"మీ బ్రౌజర్‌లో"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"సరే"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"మీ బ్రౌజర్‌లో ఇక్కడ యాప్‌లను వేగంగా తెరవండి"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 43cee41f5a15..9071bfb66e92 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"เปิดในเบราว์เซอร์"</string>
<string name="new_window_text" msgid="6318648868380652280">"หน้าต่างใหม่"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"จัดการหน้าต่าง"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"เปลี่ยนสัดส่วนการแสดงผล"</string>
<string name="close_text" msgid="4986518933445178928">"ปิด"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ในแอป"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"ในเบราว์เซอร์"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ตกลง"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"เปิดแอปในเบราว์เซอร์ได้อย่างรวดเร็วที่นี่"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 428499532005..a00f7839186b 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Buksan sa browser"</string>
<string name="new_window_text" msgid="6318648868380652280">"Bagong Window"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Pamahalaan ang Mga Window"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Baguhin ang aspect ratio"</string>
<string name="close_text" msgid="4986518933445178928">"Isara"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sa app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Sa iyong browser"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Mabilis na buksan ang mga app sa iyong browser dito"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 7eac4a8e4ffb..8310a66e9d33 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Tarayıcıda aç"</string>
<string name="new_window_text" msgid="6318648868380652280">"Yeni Pencere"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Pencereleri yönet"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"En boy oranını değiştir"</string>
<string name="close_text" msgid="4986518933445178928">"Kapat"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranın Yarısına Tuttur"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Etkileyici"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Geri yükle"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ekranı kapla"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Geri yükle"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tuttur"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Uygulamada"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Tarayıcınızda"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"Tamam"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Buradan tarayıcınızda uygulamaları hızlıca açın"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 5fb14bf50e10..624a19e67724 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Відкрити у вебпереглядачі"</string>
<string name="new_window_text" msgid="6318648868380652280">"Нове вікно"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Керувати вікнами"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Змінити формат"</string>
<string name="close_text" msgid="4986518933445178928">"Закрити"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Зафіксувати екран"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалістичність"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Відновити"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Розгорнути"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Відновити"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Закріпити ліворуч"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У додатку"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"У вебпереглядачі"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Швидко відкривайте додатки у вебпереглядачі"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index bb0358f12b7a..2ccaf50fee21 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"براؤزر میں کھولیں"</string>
<string name="new_window_text" msgid="6318648868380652280">"نئی ونڈو"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"‏‫Windows کا نظم کریں"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"تناسبی شرح کو تبدیل کریں"</string>
<string name="close_text" msgid="4986518933445178928">"بند کریں"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"اسکرین کا اسناپ شاٹ لیں"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"عمیق"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بحال کریں"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بڑا کریں"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"بحال کریں"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"دائیں منتقل کریں"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ایپ میں"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"آپ کے براؤزر میں"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"ٹھیک ہے"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"اپنے براؤزر میں ایپس کو فوری طور پر یہاں کھولیں"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 0648dd1c1bb8..88edc929528b 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Brauzerda ochish"</string>
<string name="new_window_text" msgid="6318648868380652280">"Yangi oyna"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Oynalarni boshqarish"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Tomonlar nisbatini oʻzgartirish"</string>
<string name="close_text" msgid="4986518933445178928">"Yopish"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranni biriktirish"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Tiklash"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Yoyish"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Tiklash"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chapga tortish"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ilovada"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Brauzerda"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Brauzerda ilovalarni shu yerda tezkor oching"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index dda2225b5f3e..c1c7653ce90c 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Mở trong trình duyệt"</string>
<string name="new_window_text" msgid="6318648868380652280">"Cửa sổ mới"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Quản lý cửa sổ"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Thay đổi tỷ lệ khung hình"</string>
<string name="close_text" msgid="4986518933445178928">"Đóng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Điều chỉnh kích thước màn hình"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Hiển thị tối đa"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Khôi phục"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Phóng to tối đa"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Khôi phục"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Di chuyển nhanh sang trái"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Trong ứng dụng"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Trên trình duyệt"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"OK"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Mở nhanh các ứng dụng trong trình duyệt tại đây"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 2fb3f5ab5ea4..83e15d8cbdf6 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -127,6 +127,7 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"在浏览器中打开"</string>
<string name="new_window_text" msgid="6318648868380652280">"新窗口"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"管理窗口"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"更改宽高比"</string>
<string name="close_text" msgid="4986518933445178928">"关闭"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string>
@@ -144,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在此应用内"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"在浏览器中"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"确定"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在此处快速在浏览器中打开应用"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 1d7fb4c4c6e2..f60b3efc6f38 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string>
<string name="new_window_text" msgid="6318648868380652280">"新視窗"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"身歷其境"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"還原"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"貼齊左邊"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在應用程式內"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"在瀏覽器中"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"確定"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在此透過瀏覽器快速開啟應用程式"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 8083e378bf29..b2227deeccc3 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string>
<string name="new_window_text" msgid="6318648868380652280">"新視窗"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更顯示比例"</string>
<string name="close_text" msgid="4986518933445178928">"關閉"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"還原"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"靠左對齊"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"使用應用程式"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"使用瀏覽器"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"確定"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"在這個瀏覽器中快速開啟應用程式"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 092efd6593fc..10d904fa17d2 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -127,16 +127,15 @@
<string name="open_in_browser_text" msgid="9181692926376072904">"Vula kubhrawuza"</string>
<string name="new_window_text" msgid="6318648868380652280">"Iwindi Elisha"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"Phatha Amawindi"</string>
+ <string name="change_aspect_ratio_text" msgid="9104456064548212806">"Shintsha ukubukeka kwesilinganiselo"</string>
<string name="close_text" msgid="4986518933445178928">"Vala"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Thwebula Isikrini"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string>
- <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Okugxilile"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Buyisela"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Khulisa"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Buyisela"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chofoza kwesobunxele"</string>
@@ -146,4 +145,5 @@
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ku-app"</string>
<string name="open_by_default_dialog_in_browser_text" msgid="8042769465958497081">"Kubhrawuza yakho"</string>
<string name="open_by_default_dialog_dismiss_button_text" msgid="3487238795534582291">"KULUNGILE"</string>
+ <string name="desktop_windowing_app_to_web_education_text" msgid="1599668769538703570">"Ngokushesha vula ama-app ebhrawuzeni yakho lapha"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 8f1ef6c7e49e..012579a6d40c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -301,6 +301,8 @@
<string name="screenshot_text">Screenshot</string>
<!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
<string name="open_in_browser_text">Open in browser</string>
+ <!-- Accessibility text for the handle menu open in app button [CHAR LIMIT=NONE] -->
+ <string name="open_in_app_text">Open in App</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
<string name="new_window_text">New Window</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
index e21bf8fb723c..93e635dd937c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
@@ -16,4 +16,4 @@
package com.android.wm.shell.shared;
-parcelable GroupedRecentTaskInfo; \ No newline at end of file
+parcelable GroupedTaskInfo; \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 65e079ef4f72..03e0ab0591a1 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -17,7 +17,8 @@
package com.android.wm.shell.shared;
import android.annotation.IntDef;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,69 +28,91 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.shared.split.SplitBounds;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
- * Simple container for recent tasks. May contain either a single or pair of tasks.
+ * Simple container for recent tasks which should be presented as a single task within the
+ * Overview UI.
*/
-public class GroupedRecentTaskInfo implements Parcelable {
+public class GroupedTaskInfo implements Parcelable {
- public static final int TYPE_SINGLE = 1;
+ public static final int TYPE_FULLSCREEN = 1;
public static final int TYPE_SPLIT = 2;
public static final int TYPE_FREEFORM = 3;
@IntDef(prefix = {"TYPE_"}, value = {
- TYPE_SINGLE,
+ TYPE_FULLSCREEN,
TYPE_SPLIT,
TYPE_FREEFORM
})
public @interface GroupType {}
+ /**
+ * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
+ * TYPE_FREEFORM.
+ */
+ @GroupType
+ protected final int mType;
+
+ /**
+ * The list of tasks associated with this single recent task info.
+ * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview
+ * TYPE_SPLIT: Contains the two split roots of each side
+ * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode
+ */
@NonNull
- private final ActivityManager.RecentTaskInfo[] mTasks;
+ protected final List<TaskInfo> mTasks;
+
+ /**
+ * Only set for TYPE_SPLIT.
+ *
+ * Information about the split bounds.
+ */
@Nullable
- private final SplitBounds mSplitBounds;
- @GroupType
- private final int mType;
- // TODO(b/348332802): move isMinimized inside each Task object instead once we have a
- // replacement for RecentTaskInfo
- private final int[] mMinimizedTaskIds;
+ protected final SplitBounds mSplitBounds;
/**
- * Create new for a single task
+ * Only set for TYPE_FREEFORM.
+ *
+ * TODO(b/348332802): move isMinimized inside each Task object instead once we have a
+ * replacement for RecentTaskInfo
*/
- public static GroupedRecentTaskInfo forSingleTask(
- @NonNull ActivityManager.RecentTaskInfo task) {
- return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null,
- TYPE_SINGLE, null /* minimizedFreeformTasks */);
+ @Nullable
+ protected final int[] mMinimizedTaskIds;
+
+ /**
+ * Create new for a stack of fullscreen tasks
+ */
+ public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
+ return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN,
+ null /* minimizedFreeformTasks */);
}
/**
* Create new for a pair of tasks in split screen
*/
- public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1,
- @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) {
- return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2},
- splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */);
+ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
+ @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
+ return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT,
+ null /* minimizedFreeformTasks */);
}
/**
* Create new for a group of freeform tasks
*/
- public static GroupedRecentTaskInfo forFreeformTasks(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
- @NonNull Set<Integer> minimizedFreeformTasks) {
- return new GroupedRecentTaskInfo(
- tasks,
- null /* splitBounds */,
- TYPE_FREEFORM,
+ public static GroupedTaskInfo forFreeformTasks(
+ @NonNull List<TaskInfo> tasks,
+ @NonNull Set<Integer> minimizedFreeformTasks) {
+ return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM,
minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
}
- private GroupedRecentTaskInfo(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ private GroupedTaskInfo(
+ @NonNull List<TaskInfo> tasks,
@Nullable SplitBounds splitBounds,
@GroupType int type,
@Nullable int[] minimizedFreeformTaskIds) {
@@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable {
ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
}
- private static void ensureAllMinimizedIdsPresent(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ private void ensureAllMinimizedIdsPresent(
+ @NonNull List<TaskInfo> tasks,
@Nullable int[] minimizedFreeformTaskIds) {
if (minimizedFreeformTaskIds == null) {
return;
}
if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
- taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) {
+ taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) {
throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
}
}
- GroupedRecentTaskInfo(Parcel parcel) {
- mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR);
+ protected GroupedTaskInfo(@NonNull Parcel parcel) {
+ mTasks = new ArrayList();
+ final int numTasks = parcel.readInt();
+ for (int i = 0; i < numTasks; i++) {
+ mTasks.add(new TaskInfo(parcel));
+ }
mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
mType = parcel.readInt();
mMinimizedTaskIds = parcel.createIntArray();
}
/**
- * Get primary {@link ActivityManager.RecentTaskInfo}
+ * Get primary {@link RecentTaskInfo}
*/
@NonNull
- public ActivityManager.RecentTaskInfo getTaskInfo1() {
- return mTasks[0];
+ public TaskInfo getTaskInfo1() {
+ return mTasks.getFirst();
}
/**
- * Get secondary {@link ActivityManager.RecentTaskInfo}.
+ * Get secondary {@link RecentTaskInfo}.
*
* Used in split screen.
*/
@Nullable
- public ActivityManager.RecentTaskInfo getTaskInfo2() {
- if (mTasks.length > 1) {
- return mTasks[1];
+ public TaskInfo getTaskInfo2() {
+ if (mTasks.size() > 1) {
+ return mTasks.get(1);
}
return null;
}
/**
- * Get all {@link ActivityManager.RecentTaskInfo}s grouped together.
+ * Get all {@link RecentTaskInfo}s grouped together.
*/
@NonNull
- public List<ActivityManager.RecentTaskInfo> getTaskInfoList() {
- return Arrays.asList(mTasks);
+ public List<TaskInfo> getTaskInfoList() {
+ return mTasks;
}
/**
@@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable {
return mType;
}
+ @Nullable
public int[] getMinimizedTaskIds() {
return mMinimizedTaskIds;
}
@Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof GroupedTaskInfo)) {
+ return false;
+ }
+ GroupedTaskInfo other = (GroupedTaskInfo) obj;
+ return mType == other.mType
+ && Objects.equals(mTasks, other.mTasks)
+ && Objects.equals(mSplitBounds, other.mSplitBounds)
+ && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds));
+ }
+
+ @Override
public String toString() {
StringBuilder taskString = new StringBuilder();
- for (int i = 0; i < mTasks.length; i++) {
+ for (int i = 0; i < mTasks.size(); i++) {
if (i == 0) {
taskString.append("Task");
} else {
taskString.append(", Task");
}
- taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i]));
+ taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i)));
}
if (mSplitBounds != null) {
taskString.append(", SplitBounds: ").append(mSplitBounds);
}
taskString.append(", Type=");
switch (mType) {
- case TYPE_SINGLE:
- taskString.append("TYPE_SINGLE");
+ case TYPE_FULLSCREEN:
+ taskString.append("TYPE_FULLSCREEN");
break;
case TYPE_SPLIT:
taskString.append("TYPE_SPLIT");
@@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
return taskString.toString();
}
- private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
+ private String getTaskInfo(TaskInfo taskInfo) {
if (taskInfo == null) {
return null;
}
@@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeTypedArray(mTasks, flags);
+ // We don't use the parcel list methods because we want to only write the TaskInfo state
+ // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
+ parcel.writeInt(mTasks.size());
+ for (int i = 0; i < mTasks.size(); i++) {
+ mTasks.get(i).writeTaskToParcel(parcel, flags);
+ }
parcel.writeTypedObject(mSplitBounds, flags);
parcel.writeInt(mType);
parcel.writeIntArray(mMinimizedTaskIds);
@@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable {
return 0;
}
- public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR =
- new Creator<GroupedRecentTaskInfo>() {
- public GroupedRecentTaskInfo createFromParcel(Parcel source) {
- return new GroupedRecentTaskInfo(source);
+ public static final Creator<GroupedTaskInfo> CREATOR = new Creator() {
+ @Override
+ public GroupedTaskInfo createFromParcel(Parcel in) {
+ return new GroupedTaskInfo(in);
}
- public GroupedRecentTaskInfo[] newArray(int size) {
- return new GroupedRecentTaskInfo[size];
+
+ @Override
+ public GroupedTaskInfo[] newArray(int size) {
+ return new GroupedTaskInfo[size];
}
};
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index fc3dc1465dff..f93b35e868f6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -20,7 +20,7 @@ import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.*
+import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
@@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils {
@JvmStatic
fun tearDown() {
- val latch = CountDownLatch(1)
- animationThreadHandler.post {
+ if (Looper.myLooper() == animationThreadHandler.looper) {
animatorTestHelpers.keys.forEach { it.cancel() }
- latch.countDown()
+ } else {
+ val latch = CountDownLatch(1)
+ animationThreadHandler.post {
+ animatorTestHelpers.keys.forEach { it.cancel() }
+ latch.countDown()
+ }
+ latch.await(5, TimeUnit.SECONDS)
}
- latch.await()
-
animatorTestHelpers.clear()
animators.clear()
allAnimatedObjects.clear()
@@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils {
* Returns all of the values that have ever been reported to update listeners, per property.
*/
@Suppress("UNCHECKED_CAST")
- fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
- UpdateFramesPerProperty<T> {
+ fun <T : Any> getAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>
+ ): UpdateFramesPerProperty<T> {
return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
index 7086691e7431..bd129a28f049 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
@@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
onLeft = initialLocationOnLeft
screenCenterX = screenSizeProvider.invoke().x / 2
dismissZone = getExclusionRect()
+ listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT)
}
/** View has moved to [x] and [y] screen coordinates */
@@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
/** Get width for exclusion rect where dismiss takes over drag */
protected abstract fun getExclusionRectWidth(): Float
+
/** Get height for exclusion rect where dismiss takes over drag */
protected abstract fun getExclusionRectHeight(): Float
@@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
/** Receive updates on location changes */
interface LocationChangeListener {
+ /** Bubble bar dragging has started. Includes the initial location of the bar */
+ fun onStart(location: BubbleBarLocation) {}
+
/**
* Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
* progress.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 191875d38daf..84a22b873aaf 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.shared.bubbles
+import android.annotation.IntDef
import android.os.Parcel
import android.os.Parcelable
@@ -60,4 +61,36 @@ enum class BubbleBarLocation : Parcelable {
override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
}
}
+
+ /** Define set of constants that allow to determine why location changed. */
+ @IntDef(
+ UpdateSource.DRAG_BAR,
+ UpdateSource.DRAG_BUBBLE,
+ UpdateSource.DRAG_EXP_VIEW,
+ UpdateSource.A11Y_ACTION_BAR,
+ UpdateSource.A11Y_ACTION_BUBBLE,
+ UpdateSource.A11Y_ACTION_EXP_VIEW,
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class UpdateSource {
+ companion object {
+ /** Location changed from dragging the bar */
+ const val DRAG_BAR = 1
+
+ /** Location changed from dragging the bubble */
+ const val DRAG_BUBBLE = 2
+
+ /** Location changed from dragging the expanded view */
+ const val DRAG_EXP_VIEW = 3
+
+ /** Location changed via a11y action on the bar */
+ const val A11Y_ACTION_BAR = 4
+
+ /** Location changed via a11y action on the bubble */
+ const val A11Y_ACTION_BUBBLE = 5
+
+ /** Location changed via a11y action on the expanded view */
+ const val A11Y_ACTION_EXP_VIEW = 6
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 65132fe89063..7243ea36b137 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -20,7 +20,9 @@ package com.android.wm.shell.apptoweb
import android.content.Context
import android.content.Intent
+import android.content.Intent.ACTION_VIEW
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER
import android.content.pm.PackageManager
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState
@@ -31,7 +33,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup
private const val TAG = "AppToWebUtils"
private val GenericBrowserIntent = Intent()
- .setAction(Intent.ACTION_VIEW)
+ .setAction(ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.parse("http:"))
@@ -67,6 +69,20 @@ fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? {
}
/**
+ * Returns intent if there is a non-browser application available to handle the uri. Otherwise,
+ * returns null.
+ */
+fun getAppIntent(uri: Uri, packageManager: PackageManager): Intent? {
+ val intent = Intent(ACTION_VIEW, uri).apply {
+ flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
+ }
+ // If there is no application available to handle intent, return null
+ val component = intent.resolveActivity(packageManager) ?: return null
+ intent.setComponent(component)
+ return intent
+}
+
+/**
* Returns the [DomainVerificationUserState] of the user associated with the given
* [DomainVerificationManager] and the given package.
*/
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 b9a305062b46..ce7a97703f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
+import static android.window.BackEvent.EDGE_NONE;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -533,7 +534,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
- mShouldStartOnNextMoveEvent = true;
+ if (swipeEdge == EDGE_NONE) {
+ // start animation immediately for non-gestural sources (without ACTION_MOVE
+ // events)
+ mThresholdCrossed = true;
+ onGestureStarted(touchX, touchY, swipeEdge);
+ mShouldStartOnNextMoveEvent = false;
+ } else {
+ mShouldStartOnNextMoveEvent = true;
+ }
}
} else if (keyAction == MotionEvent.ACTION_MOVE) {
if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
@@ -1074,6 +1083,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mCurrentTracker.updateStartLocation();
BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
dispatchOnBackStarted(mActiveCallback, startEvent);
+ // TODO(b/373544911): onBackStarted is dispatched here so that
+ // WindowOnBackInvokedDispatcher knows about the back navigation and intercepts touch
+ // events while it's active. It would be cleaner and safer to disable multitouch
+ // altogether (same as in gesture-nav).
+ dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent);
}
}
@@ -1108,7 +1122,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final BackMotionEvent backFinish = mCurrentTracker
.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
+ if (mCurrentTracker.isFinished()) {
// if the down -> up gesture happened before animation
// start, we have to trigger the uninterruptible transition
// to finish the back animation.
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 14f8cc74bfc5..39dc26797a81 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
@@ -740,8 +740,10 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Update bubble bar location and trigger and update to listeners
*/
- public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+ @BubbleBarLocation.UpdateSource int source) {
if (canShowAsBubbleBar()) {
+ BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
mLayerView.updateExpandedView();
@@ -749,13 +751,47 @@ public class BubbleController implements ConfigurationChangeListener,
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+
+ logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
+ }
+ }
+
+ private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
+ BubbleBarLocation previous,
+ @BubbleBarLocation.UpdateSource int source) {
+ if (mLayerView == null) {
+ return;
+ }
+ boolean isRtl = mLayerView.isLayoutRtl();
+ boolean wasLeft = previous.isOnLeft(isRtl);
+ boolean onLeft = location.isOnLeft(isRtl);
+ if (wasLeft == onLeft) {
+ // No changes, skip logging
+ return;
+ }
+ switch (source) {
+ case BubbleBarLocation.UpdateSource.DRAG_BAR:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+ // TODO(b/349845968): move logging from BubbleBarLayerView to here
+ break;
}
}
/**
* Animate bubble bar to the given location. The location change is transient. It does not
* update the state of the bubble bar.
- * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ * To update bubble bar pinned location, use
+ * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
*/
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
if (canShowAsBubbleBar()) {
@@ -1259,6 +1295,14 @@ public class BubbleController implements ConfigurationChangeListener,
// We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
// so re-expand to whatever is selected.
showExpandedViewForBubbleBar();
+ if (bubbleKey.equals(selectedBubbleKey)) {
+ // We dragged the selected bubble to dismiss, log switch event
+ if (mBubbleData.getSelectedBubble() instanceof Bubble) {
+ // Log only bubbles as overflow can't be dragged
+ mLogger.log((Bubble) mBubbleData.getSelectedBubble(),
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ }
+ }
}
}
@@ -1301,10 +1345,16 @@ public class BubbleController implements ConfigurationChangeListener,
public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) {
mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
+ boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded());
+
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
mLayerView.showExpandedView(mBubbleData.getOverflow());
- mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ if (wasExpanded) {
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ } else {
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ }
return;
}
@@ -1316,7 +1366,11 @@ public class BubbleController implements ConfigurationChangeListener,
// already in the stack
mBubbleData.setSelectedBubbleFromLauncher(b);
mLayerView.showExpandedView(b);
- mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ if (wasExpanded) {
+ mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ } else {
+ mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
+ }
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// TODO: (b/271468319) handle overflow
} else {
@@ -2045,6 +2099,9 @@ public class BubbleController implements ConfigurationChangeListener,
// Only need to update the layer view if we're currently expanded for selection changes.
if (mLayerView != null && mLayerView.isExpanded()) {
mLayerView.showExpandedView(selectedBubble);
+ if (selectedBubble instanceof Bubble bubble) {
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
+ }
}
}
};
@@ -2568,9 +2625,10 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void setBubbleBarLocation(BubbleBarLocation location) {
+ public void setBubbleBarLocation(BubbleBarLocation location,
+ @BubbleBarLocation.UpdateSource int source) {
mMainExecutor.execute(() ->
- mController.setBubbleBarLocation(location));
+ mController.setBubbleBarLocation(location, source));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index ec4854b47aff..6423eed59165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -32,7 +32,10 @@ interface BubbleExpandedViewManager {
fun isStackExpanded(): Boolean
fun isShowingAsBubbleBar(): Boolean
fun hideCurrentInputMethod()
- fun updateBubbleBarLocation(location: BubbleBarLocation)
+ fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ )
companion object {
/**
@@ -82,8 +85,11 @@ interface BubbleExpandedViewManager {
controller.hideCurrentInputMethod()
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
- controller.bubbleBarLocation = location
+ override fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ ) {
+ controller.setBubbleBarLocation(location, source)
}
}
}
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 1855b938f48e..9c2d35431554 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
@@ -44,7 +44,7 @@ interface IBubbles {
oneway void showUserEducation(in int positionX, in int positionY) = 8;
- oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+ oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9;
oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 0ce651c3f1fe..3764bcd42ac6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -222,7 +222,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate());
}
- mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+ mMenuViewController = new BubbleBarMenuViewController(mContext, mHandleView, this);
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@Override
public void onMenuVisibilityChanged(boolean visible) {
@@ -241,12 +241,14 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (mListener != null) {
mListener.onUnBubbleConversation(bubble.getKey());
}
+ mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_APP_MENU_OPT_OUT);
}
@Override
public void onOpenAppSettings(Bubble bubble) {
mManager.collapseStack();
mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+ mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_APP_MENU_GO_TO_SETTINGS);
}
@Override
@@ -635,11 +637,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
return true;
}
if (action == R.id.action_move_bubble_bar_left) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
if (action == R.id.action_move_bubble_bar_right) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index e781c07f01a7..712e41b0b3c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -17,17 +17,18 @@ package com.android.wm.shell.bubbles.bar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Path;
-import android.graphics.RectF;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewOutlineProvider;
import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.IntProperty;
import androidx.core.content.ContextCompat;
import com.android.wm.shell.R;
@@ -37,14 +38,33 @@ import com.android.wm.shell.R;
*/
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
- // Path used to draw the dots
- private final Path mPath = new Path();
+ /** Custom property to set handle color. */
+ private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>(
+ "handleColor") {
+ @Override
+ public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) {
+ bubbleBarHandleView.setHandleColor(color);
+ }
+
+ @Override
+ public Integer get(BubbleBarHandleView bubbleBarHandleView) {
+ return bubbleBarHandleView.getHandleColor();
+ }
+ };
+
+ @VisibleForTesting
+ final Paint mHandlePaint = new Paint();
private final @ColorInt int mHandleLightColor;
private final @ColorInt int mHandleDarkColor;
- private @ColorInt int mCurrentColor;
+ private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance();
+ private final float mHandleHeight;
+ private final float mHandleWidth;
+ private float mCurrentHandleHeight;
+ private float mCurrentHandleWidth;
@Nullable
private ObjectAnimator mColorChangeAnim;
+ private @ColorInt int mRegionSamplerColor;
public BubbleBarHandleView(Context context) {
this(context, null /* attrs */);
@@ -61,30 +81,52 @@ public class BubbleBarHandleView extends View {
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- final int handleHeight = getResources().getDimensionPixelSize(
+ mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mHandlePaint.setStyle(Paint.Style.FILL);
+ mHandlePaint.setColor(0);
+ mHandleHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_handle_height);
+ mHandleWidth = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_width);
mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
mHandleDarkColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_dark);
-
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- final int handleCenterY = view.getHeight() / 2;
- final int handleTop = handleCenterY - handleHeight / 2;
- final int handleBottom = handleTop + handleHeight;
- final int radius = handleHeight / 2;
- RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom);
- mPath.reset();
- mPath.addRoundRect(handle, radius, radius, Path.Direction.CW);
- outline.setPath(mPath);
- }
- });
+ mCurrentHandleHeight = mHandleHeight;
+ mCurrentHandleWidth = mHandleWidth;
setContentDescription(getResources().getString(R.string.handle_text));
}
+ private void setHandleColor(int color) {
+ mHandlePaint.setColor(color);
+ invalidate();
+ }
+
+ private int getHandleColor() {
+ return mHandlePaint.getColor();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ float handleLeft = (getWidth() - mCurrentHandleWidth) / 2;
+ float handleRight = handleLeft + mCurrentHandleWidth;
+ float handleCenterY = (float) getHeight() / 2;
+ float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2);
+ float handleBottom = handleTop + mCurrentHandleHeight;
+ float cornerRadius = mCurrentHandleHeight / 2;
+ canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius,
+ cornerRadius, mHandlePaint);
+ }
+
+ /** Sets handle width, height and color. Does not change the layout properties */
+ private void setHandleProperties(float width, float height, int color) {
+ mCurrentHandleHeight = height;
+ mCurrentHandleWidth = width;
+ mHandlePaint.setColor(color);
+ invalidate();
+ }
+
/**
* Updates the handle color.
*
@@ -94,15 +136,15 @@ public class BubbleBarHandleView extends View {
*/
public void updateHandleColor(boolean isRegionDark, boolean animated) {
int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
- if (newColor == mCurrentColor) {
+ if (newColor == mRegionSamplerColor) {
return;
}
+ mRegionSamplerColor = newColor;
if (mColorChangeAnim != null) {
mColorChangeAnim.cancel();
}
- mCurrentColor = newColor;
if (animated) {
- mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+ mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor);
mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -112,7 +154,39 @@ public class BubbleBarHandleView extends View {
mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
mColorChangeAnim.start();
} else {
- setBackgroundColor(newColor);
+ setHandleColor(newColor);
}
}
+
+ /** Returns handle padding top. */
+ public int getHandlePaddingTop() {
+ return (getHeight() - getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_height)) / 2;
+ }
+
+ /** Animates handle for the bubble menu. */
+ public void animateHandleForMenu(float progress, float widthDelta, float heightDelta,
+ int menuColor) {
+ float currentWidth = mHandleWidth + widthDelta * progress;
+ float currentHeight = mHandleHeight + heightDelta * progress;
+ int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor);
+ setHandleProperties(currentWidth, currentHeight, color);
+ setTranslationY(heightDelta * progress / 2);
+ }
+
+ /** Restores all the properties that were animated to the default values. */
+ public void restoreAnimationDefaults() {
+ setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor);
+ setTranslationY(0);
+ }
+
+ /** Returns the handle height. */
+ public int getHandleHeight() {
+ return (int) mHandleHeight;
+ }
+
+ /** Returns the handle width. */
+ public int getHandleWidth() {
+ return (int) mHandleWidth;
+ }
}
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 402818c80b01..0c05e3c5115c 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
+import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import android.annotation.Nullable;
import android.content.Context;
@@ -66,8 +67,6 @@ public class BubbleBarLayerView extends FrameLayout
private static final String TAG = BubbleBarLayerView.class.getSimpleName();
- private static final float SCRIM_ALPHA = 0.2f;
-
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
@@ -124,18 +123,7 @@ public class BubbleBarLayerView extends FrameLayout
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(
- new BaseBubblePinController.LocationChangeListener() {
- @Override
- public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
- mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
- }
-
- @Override
- public void onRelease(@NonNull BubbleBarLocation location) {
- mBubbleController.setBubbleBarLocation(location);
- }
- });
+ mBubbleExpandedViewPinController.setListener(new LocationChangeListener());
setOnClickListener(view -> hideModalOrCollapse());
}
@@ -238,11 +226,7 @@ public class BubbleBarLayerView extends FrameLayout
DragListener dragListener = inDismiss -> {
if (inDismiss && mExpandedBubble != null) {
mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
- if (mExpandedBubble instanceof Bubble) {
- // Only a bubble can be dragged to dismiss
- mBubbleLogger.log((Bubble) mExpandedBubble,
- BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
- }
+ logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
}
};
mDragController = new BubbleBarExpandedViewDragController(
@@ -401,7 +385,7 @@ public class BubbleBarLayerView extends FrameLayout
if (show) {
mScrimView.animate()
.setInterpolator(ALPHA_IN)
- .alpha(SCRIM_ALPHA)
+ .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
.start();
} else {
mScrimView.animate()
@@ -423,10 +407,48 @@ public class BubbleBarLayerView extends FrameLayout
}
}
+ /**
+ * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}.
+ * <p>
+ * Skips logging if it is {@link BubbleOverflow}.
+ */
+ private void logBubbleEvent(BubbleLogger.Event event) {
+ if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) {
+ mBubbleLogger.log(bubble, event);
+ }
+ }
+
@Nullable
@VisibleForTesting
public BubbleBarExpandedViewDragController getDragController() {
return mDragController;
}
+ private class LocationChangeListener implements
+ BaseBubblePinController.LocationChangeListener {
+
+ private BubbleBarLocation mInitialLocation;
+
+ @Override
+ public void onStart(@NonNull BubbleBarLocation location) {
+ mInitialLocation = location;
+ }
+
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
+ if (location != mInitialLocation) {
+ BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
+ ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW;
+ logBubbleEvent(event);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index 52b807abddd6..99e20097e61c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.bubbles.bar;
import android.annotation.ColorInt;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -43,13 +42,15 @@ import java.util.ArrayList;
*/
public class BubbleBarMenuView extends LinearLayout {
- public static final Object DISMISS_ACTION_TAG = new Object();
-
private ViewGroup mBubbleSectionView;
private ViewGroup mActionsSectionView;
private ImageView mBubbleIconView;
private ImageView mBubbleDismissIconView;
private TextView mBubbleTitleView;
+ // The animation has three stages. Each stage transition lasts until the animation ends. In
+ // stage 1, the title item content fades in. In stage 2, the background of the option items
+ // fades in. In stage 3, the option item content fades in.
+ private static final int SHOW_MENU_STAGES_COUNT = 3;
public BubbleBarMenuView(Context context) {
this(context, null /* attrs */);
@@ -100,6 +101,35 @@ public class BubbleBarMenuView extends LinearLayout {
}
}
+ /** Animates the menu from the specified start scale. */
+ public void animateFromStartScale(float currentScale, float progress) {
+ int menuItemElevation = getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_menu_elevation);
+ setScaleX(currentScale);
+ setScaleY(currentScale);
+ setAlphaForTitleViews(progress);
+ mBubbleSectionView.setElevation(menuItemElevation * progress);
+ float actionsBackgroundAlpha = Math.max(0,
+ (progress - (float) 1 / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1));
+ float actionItemsAlpha = Math.max(0,
+ (progress - (float) 2 / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT);
+ mActionsSectionView.setAlpha(actionsBackgroundAlpha);
+ mActionsSectionView.setElevation(menuItemElevation * actionsBackgroundAlpha);
+ setMenuItemViewsAlpha(actionItemsAlpha);
+ }
+
+ private void setAlphaForTitleViews(float alpha) {
+ mBubbleIconView.setAlpha(alpha);
+ mBubbleTitleView.setAlpha(alpha);
+ mBubbleDismissIconView.setAlpha(alpha);
+ }
+
+ private void setMenuItemViewsAlpha(float alpha) {
+ for (int i = mActionsSectionView.getChildCount() - 1; i >= 0; i--) {
+ mActionsSectionView.getChildAt(i).setAlpha(alpha);
+ }
+ }
+
/** Update menu details with bubble info */
void updateInfo(Bubble bubble) {
if (bubble.getIcon() != null) {
@@ -123,9 +153,6 @@ public class BubbleBarMenuView extends LinearLayout {
R.layout.bubble_bar_menu_item, mActionsSectionView, false);
itemView.update(action.mIcon, action.mTitle, action.mTint);
itemView.setOnClickListener(action.mOnClick);
- if (action.mTag != null) {
- itemView.setTag(action.mTag);
- }
mActionsSectionView.addView(itemView);
}
}
@@ -159,6 +186,11 @@ public class BubbleBarMenuView extends LinearLayout {
return mBubbleSectionView.getAlpha();
}
+ /** Return title menu item height. */
+ public float getTitleItemHeight() {
+ return mBubbleSectionView.getHeight();
+ }
+
/**
* Menu action details used to create menu items
*/
@@ -166,8 +198,6 @@ public class BubbleBarMenuView extends LinearLayout {
private Icon mIcon;
private @ColorInt int mTint;
private String mTitle;
- @Nullable
- private Object mTag;
private OnClickListener mOnClick;
MenuAction(Icon icon, String title, OnClickListener onClick) {
@@ -180,14 +210,5 @@ public class BubbleBarMenuView extends LinearLayout {
this.mTint = tint;
this.mOnClick = onClick;
}
-
- MenuAction(Icon icon, String title, @ColorInt int tint, @Nullable Object tag,
- OnClickListener onClick) {
- this.mIcon = icon;
- this.mTitle = title;
- this.mTint = tint;
- this.mTag = tag;
- this.mOnClick = onClick;
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 5ed01b66ec67..9dd0cae20370 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -15,6 +15,9 @@
*/
package com.android.wm.shell.bubbles.bar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,13 +29,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
+import com.android.app.animation.Interpolators;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.shared.animation.PhysicsAnimator;
import java.util.ArrayList;
@@ -40,22 +40,26 @@ import java.util.ArrayList;
* Manages bubble bar expanded view menu presentation and animations
*/
class BubbleBarMenuViewController {
- private static final float MENU_INITIAL_SCALE = 0.5f;
+
+ private static final float WIDTH_SWAP_FRACTION = 0.4F;
+ private static final long MENU_ANIMATION_DURATION = 600;
+
private final Context mContext;
private final ViewGroup mRootView;
+ private final BubbleBarHandleView mHandleView;
private @Nullable Listener mListener;
private @Nullable Bubble mBubble;
private @Nullable BubbleBarMenuView mMenuView;
/** A transparent view used to intercept touches to collapse menu when presented */
private @Nullable View mScrimView;
- private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
- private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+ private @Nullable ValueAnimator mMenuAnimator;
+
- BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+ BubbleBarMenuViewController(Context context, BubbleBarHandleView handleView,
+ ViewGroup rootView) {
mContext = context;
mRootView = rootView;
- mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ mHandleView = handleView;
}
/** Tells if the menu is visible or being animated */
@@ -81,20 +85,21 @@ class BubbleBarMenuViewController {
if (mMenuView == null || mScrimView == null) {
setupMenu();
}
- cancelAnimations();
- mMenuView.setVisibility(View.VISIBLE);
- mScrimView.setVisibility(View.VISIBLE);
- Runnable endActions = () -> {
- mMenuView.getChildAt(0).requestAccessibilityFocus();
- if (mListener != null) {
- mListener.onMenuVisibilityChanged(true /* isShown */);
+ runOnMenuIsMeasured(() -> {
+ mMenuView.setVisibility(View.VISIBLE);
+ mScrimView.setVisibility(View.VISIBLE);
+ Runnable endActions = () -> {
+ mMenuView.getChildAt(0).requestAccessibilityFocus();
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(true /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(true /* show */, endActions);
+ } else {
+ endActions.run();
}
- };
- if (animated) {
- animateTransition(true /* show */, endActions);
- } else {
- endActions.run();
- }
+ });
}
/**
@@ -103,18 +108,30 @@ class BubbleBarMenuViewController {
*/
void hideMenu(boolean animated) {
if (mMenuView == null || mScrimView == null) return;
- cancelAnimations();
- Runnable endActions = () -> {
- mMenuView.setVisibility(View.GONE);
- mScrimView.setVisibility(View.GONE);
- if (mListener != null) {
- mListener.onMenuVisibilityChanged(false /* isShown */);
+ runOnMenuIsMeasured(() -> {
+ Runnable endActions = () -> {
+ mHandleView.restoreAnimationDefaults();
+ mMenuView.setVisibility(View.GONE);
+ mScrimView.setVisibility(View.GONE);
+ mHandleView.setVisibility(View.VISIBLE);
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(false /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(false /* show */, endActions);
+ } else {
+ endActions.run();
}
- };
- if (animated) {
- animateTransition(false /* show */, endActions);
+ });
+ }
+
+ private void runOnMenuIsMeasured(Runnable action) {
+ if (mMenuView.getWidth() == 0 || mMenuView.getHeight() == 0) {
+ // the menu view is not yet measured, postpone showing the animation
+ mMenuView.post(() -> runOnMenuIsMeasured(action));
} else {
- endActions.run();
+ action.run();
}
}
@@ -125,24 +142,63 @@ class BubbleBarMenuViewController {
*/
private void animateTransition(boolean show, Runnable endActions) {
if (mMenuView == null) return;
- mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
- mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
- mMenuAnimator
- .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
- .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
- .withEndActions(() -> {
- mMenuAnimator = null;
- endActions.run();
- })
- .start();
+ float startValue = show ? 0 : 1;
+ if (mMenuAnimator != null && mMenuAnimator.isRunning()) {
+ startValue = (float) mMenuAnimator.getAnimatedValue();
+ mMenuAnimator.cancel();
+ }
+ ValueAnimator showMenuAnimation = ValueAnimator.ofFloat(startValue, show ? 1 : 0);
+ showMenuAnimation.setDuration(MENU_ANIMATION_DURATION);
+ showMenuAnimation.setInterpolator(Interpolators.EMPHASIZED);
+ showMenuAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mMenuAnimator = null;
+ endActions.run();
+ }
+ });
+ mMenuAnimator = showMenuAnimation;
+ setupAnimatorListener(showMenuAnimation);
+ showMenuAnimation.start();
}
- /** Cancel running animations */
- private void cancelAnimations() {
- if (mMenuAnimator != null) {
- mMenuAnimator.cancel();
- mMenuAnimator = null;
+ /** Setup listener that orchestrates the animation. */
+ private void setupAnimatorListener(ValueAnimator showMenuAnimation) {
+ // Getting views properties start values
+ int widthDiff = mMenuView.getWidth() - mHandleView.getHandleWidth();
+ int handleHeight = mHandleView.getHandleHeight();
+ float targetWidth = mHandleView.getHandleWidth() + widthDiff * WIDTH_SWAP_FRACTION;
+ float targetHeight = targetWidth * mMenuView.getTitleItemHeight() / mMenuView.getWidth();
+ int menuColor;
+ try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+ com.android.internal.R.attr.materialColorSurfaceBright,
+ })) {
+ menuColor = ta.getColor(0, Color.WHITE);
}
+ // Calculating deltas
+ float swapScale = targetWidth / mMenuView.getWidth();
+ float handleWidthDelta = targetWidth - mHandleView.getHandleWidth();
+ float handleHeightDelta = targetHeight - handleHeight;
+ // Setting update listener that will orchestrate the animation
+ showMenuAnimation.addUpdateListener(animator -> {
+ float animationProgress = (float) animator.getAnimatedValue();
+ boolean showHandle = animationProgress <= WIDTH_SWAP_FRACTION;
+ mHandleView.setVisibility(showHandle ? View.VISIBLE : View.GONE);
+ mMenuView.setVisibility(showHandle ? View.GONE : View.VISIBLE);
+ if (showHandle) {
+ float handleAnimationProgress = animationProgress / WIDTH_SWAP_FRACTION;
+ mHandleView.animateHandleForMenu(handleAnimationProgress, handleWidthDelta,
+ handleHeightDelta, menuColor);
+ } else {
+ mMenuView.setTranslationY(mHandleView.getHandlePaddingTop());
+ mMenuView.setPivotY(0);
+ mMenuView.setPivotX((float) mMenuView.getWidth() / 2);
+ float menuAnimationProgress =
+ (animationProgress - WIDTH_SWAP_FRACTION) / (1 - WIDTH_SWAP_FRACTION);
+ float currentMenuScale = swapScale + (1 - swapScale) * menuAnimationProgress;
+ mMenuView.animateFromStartScale(currentMenuScale, menuAnimationProgress);
+ }
+ });
}
/** Sets up and inflate menu views */
@@ -150,9 +206,6 @@ class BubbleBarMenuViewController {
// Menu view setup
mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
R.layout.bubble_bar_menu_view, mRootView, false);
- mMenuView.setAlpha(0f);
- mMenuView.setPivotY(0f);
- mMenuView.setScaleY(MENU_INITIAL_SCALE);
mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */));
if (mBubble != null) {
mMenuView.updateInfo(mBubble);
@@ -212,7 +265,6 @@ class BubbleBarMenuViewController {
Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
resources.getString(R.string.bubble_dismiss_text),
tintColor,
- BubbleBarMenuView.DISMISS_ACTION_TAG,
view -> {
hideMenu(true /* animated */);
if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
index 4abb35c2a428..193c593e2ab2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -16,8 +16,11 @@
package com.android.wm.shell.common.pip
import android.app.AppOpsManager
+import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.util.Pair
+import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.common.ShellExecutor
class PipAppOpsListener(
@@ -27,10 +30,12 @@ class PipAppOpsListener(
) {
private val mAppOpsManager: AppOpsManager = checkNotNull(
mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+ private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> =
+ PipUtils::getTopPipActivity
private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
try {
// Dismiss the PiP once the user disables the app ops setting for that package
- val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+ val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext)
val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
val userId = topPipActivityInfo.second
val appInfo = mContext.packageManager
@@ -75,4 +80,9 @@ class PipAppOpsListener(
/** Dismisses the PIP window. */
fun dismissPip()
}
+
+ @VisibleForTesting
+ fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) {
+ mTopPipActivityInfoSupplier = supplier
+ }
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index b83b5f341dda..8ef20d1d6b93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -44,7 +44,8 @@ object PipUtils {
private const val TAG = "PipUtils"
// Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
- private const val EPSILON = 1e-7
+ // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue.
+ private const val EPSILON = 0.05f
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 886330f3264a..c99d9ba862c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
*/
private boolean mIsFirstReachabilityEducationRunning;
+ private boolean mIsInDesktopMode;
+
@NonNull
private final CompatUIStatusManager mCompatUIStatusManager;
@@ -253,18 +255,19 @@ public class CompatUIController implements OnDisplaysChangedListener,
if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
-
- if (taskInfo != null && taskListener != null) {
- updateActiveTaskInfo(taskInfo);
- }
-
- // We close all the Compat UI educations in case we're in desktop mode.
- if (taskInfo.configuration == null || taskListener == null
- || isInDesktopMode(taskInfo.displayId)) {
+ mIsInDesktopMode = isInDesktopMode(taskInfo);
+ // We close all the Compat UI educations in case TaskInfo has no configuration or
+ // TaskListener or in desktop mode.
+ if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
return;
}
+ if (taskInfo != null && taskListener != null) {
+ updateActiveTaskInfo(taskInfo);
+ }
+
+
// We're showing the first reachability education so we ignore incoming TaskInfo
// until the education flow has completed or we double tap. The double-tap
// basically cancel all the onboarding flow. We don't have to ignore events in case
@@ -286,7 +289,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
// we need to ignore all the incoming TaskInfo until the education
// completes. If we come from a double tap we follow the normal flow.
final boolean topActivityPillarboxed =
- taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+ taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped();
final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
&& !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
@@ -443,7 +446,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
if (layout != null) {
- if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
mActiveCompatLayouts.remove(taskInfo.taskId);
layout.release();
} else {
@@ -456,7 +459,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
-
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -494,7 +500,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveLetterboxEduLayout != null) {
- if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
} else {
@@ -507,6 +514,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -541,7 +552,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
RestartDialogWindowManager layout =
mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
if (layout != null) {
- if (layout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
layout.release();
} else {
@@ -556,6 +567,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -594,7 +609,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mActiveReachabilityEduLayout != null) {
- if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mActiveReachabilityEduLayout.release();
mActiveReachabilityEduLayout = null;
} else {
@@ -608,6 +624,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -647,7 +667,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
if (mUserAspectRatioSettingsLayout != null) {
- if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+ if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)
+ || mIsInDesktopMode) {
mUserAspectRatioSettingsLayout.release();
mUserAspectRatioSettingsLayout = null;
} else {
@@ -660,7 +681,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
return;
}
}
-
+ if (mIsInDesktopMode) {
+ // Return if in desktop mode.
+ return;
+ }
// Create a new UI layout.
final Context context = getOrCreateDisplayContext(taskInfo.displayId);
if (context == null) {
@@ -840,8 +864,8 @@ public class CompatUIController implements OnDisplaysChangedListener,
boolean mHasShownUserAspectRatioSettingsButtonHint;
}
- private boolean isInDesktopMode(int displayId) {
- return Flags.skipCompatUiEducationInDesktopMode()
- && mInDesktopModePredicate.test(displayId);
+ private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) {
+ return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode()
+ && mInDesktopModePredicate.test(taskInfo.displayId);
}
}
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 706a67821a30..601cf70b93ed 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
@@ -67,6 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopBackNavigationTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -779,7 +780,8 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
ReturnToDragStartAnimator returnToDragStartAnimator,
- @DynamicOverride DesktopRepository desktopRepository) {
+ @DynamicOverride DesktopRepository desktopRepository,
+ DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopTilingDecorViewModel(
context,
displayController,
@@ -789,7 +791,8 @@ public abstract class WMShellModule {
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository
+ desktopRepository,
+ desktopModeEventLogger
);
}
@@ -834,14 +837,21 @@ public abstract class WMShellModule {
@Provides
static Optional<DesktopImmersiveController> provideDesktopImmersiveController(
Context context,
+ ShellInit shellInit,
Transitions transitions,
@DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
- ShellTaskOrganizer shellTaskOrganizer) {
+ ShellTaskOrganizer shellTaskOrganizer,
+ ShellCommandHandler shellCommandHandler) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopImmersiveController(
- transitions, desktopRepository, displayController, shellTaskOrganizer));
+ shellInit,
+ transitions,
+ desktopRepository,
+ displayController,
+ shellTaskOrganizer,
+ shellCommandHandler));
}
return Optional.empty();
}
@@ -906,6 +916,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DesktopBackNavigationTransitionHandler provideDesktopBackNavigationTransitionHandler(
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor,
+ DisplayController displayController) {
+ return new DesktopBackNavigationTransitionHandler(mainExecutor, animExecutor,
+ displayController);
+ }
+
+ @WMSingleton
+ @Provides
static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
Transitions transitions) {
return new DesktopModeDragAndDropTransitionHandler(transitions);
@@ -955,6 +975,7 @@ public abstract class WMShellModule {
Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
+ Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
ShellInit shellInit) {
return desktopRepository.flatMap(
repository ->
@@ -964,6 +985,7 @@ public abstract class WMShellModule {
repository,
transitions,
shellTaskOrganizer,
+ desktopMixedTransitionHandler.get(),
shellInit)));
}
@@ -976,6 +998,7 @@ public abstract class WMShellModule {
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
Optional<DesktopImmersiveController> desktopImmersiveController,
+ DesktopBackNavigationTransitionHandler desktopBackNavigationTransitionHandler,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler,
ShellInit shellInit,
@@ -992,6 +1015,7 @@ public abstract class WMShellModule {
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController.get(),
+ desktopBackNavigationTransitionHandler,
interactionJankMonitor,
handler,
shellInit,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 3a4764d45f2c..3cd5df3121c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip;
import android.content.Context;
import android.os.Handler;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -41,6 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -169,6 +171,8 @@ public abstract class Pip1Module {
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -176,7 +180,8 @@ public abstract class Pip1Module {
syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 8d1b15c1e631..78e676f8cd45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -22,6 +22,7 @@ import android.os.SystemClock;
import androidx.annotation.NonNull;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -214,6 +215,7 @@ public abstract class TvPipModule {
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -221,8 +223,9 @@ public abstract class TvPipModule {
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
new file mode 100644
index 000000000000..83b0f8413a28
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.DisplayMetrics
+import android.view.SurfaceControl.Transaction
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.MinimizeAnimator.create
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [Transitions.TransitionHandler] that handles transitions for tasks that are closing or going
+ * to back as part of back navigation. This handler is used only for animating transitions.
+ */
+class DesktopBackNavigationTransitionHandler(
+ private val mainExecutor: ShellExecutor,
+ private val animExecutor: ShellExecutor,
+ private val displayController: DisplayController,
+) : Transitions.TransitionHandler {
+
+ /** Shouldn't handle anything */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ /** Animates a transition with minimizing tasks */
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ if (!TransitionUtil.isClosingType(info.type)) return false
+
+ val animations = mutableListOf<Animator>()
+ val onAnimFinish: (Animator) -> Unit = { animator ->
+ mainExecutor.execute {
+ // Animation completed
+ animations.remove(animator)
+ if (animations.isEmpty()) {
+ // All animations completed, finish the transition
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ }
+ }
+ }
+
+ animations +=
+ info.changes
+ .filter {
+ it.mode == info.type &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
+ if (animations.isEmpty()) return false
+ animExecutor.execute { animations.forEach(Animator::start) }
+ return true
+ }
+
+ private fun createMinimizeAnimation(
+ change: TransitionInfo.Change,
+ finishTransaction: Transaction,
+ onAnimFinish: (Animator) -> Unit
+ ): Animator? {
+ val t = Transaction()
+ val sc = change.leash
+ finishTransaction.hide(sc)
+ val displayMetrics: DisplayMetrics? =
+ change.taskInfo?.let {
+ displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics
+ }
+ return displayMetrics?.let { create(it, change, t, onAnimFinish) }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index f69aa6df6a1d..1acde73e68dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -34,10 +34,13 @@ import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.transition.Transitions.TransitionObserver
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+import java.io.PrintWriter
/**
* A controller to move tasks in/out of desktop's full immersive state where the task
@@ -45,27 +48,34 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
* be transient below the status bar like in fullscreen immersive mode.
*/
class DesktopImmersiveController(
+ shellInit: ShellInit,
private val transitions: Transitions,
private val desktopRepository: DesktopRepository,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val shellCommandHandler: ShellCommandHandler,
private val transactionSupplier: () -> SurfaceControl.Transaction,
) : TransitionHandler, TransitionObserver {
constructor(
+ shellInit: ShellInit,
transitions: Transitions,
desktopRepository: DesktopRepository,
displayController: DisplayController,
shellTaskOrganizer: ShellTaskOrganizer,
+ shellCommandHandler: ShellCommandHandler,
) : this(
+ shellInit,
transitions,
desktopRepository,
displayController,
shellTaskOrganizer,
+ shellCommandHandler,
{ SurfaceControl.Transaction() }
)
- private var state: TransitionState? = null
+ @VisibleForTesting
+ var state: TransitionState? = null
@VisibleForTesting
val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
@@ -79,10 +89,21 @@ class DesktopImmersiveController(
/** A listener to invoke on animation changes during entry/exit. */
var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ fun onInit() {
+ shellCommandHandler.addDumpCallback(this::dump, this)
+ }
+
/** Starts a transition to enter full immersive state inside the desktop. */
fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start entry because transition already in progress.")
+ logV(
+ "Cannot start entry because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
val wct = WindowContainerTransaction().apply {
@@ -100,7 +121,10 @@ class DesktopImmersiveController(
fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start exit because transition already in progress.")
+ logV(
+ "Cannot start exit because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
@@ -225,14 +249,19 @@ class DesktopImmersiveController(
finishCallback: Transitions.TransitionFinishCallback
): Boolean {
val state = requireState()
- if (transition != state.transition) return false
+ check(state.transition == transition) {
+ "Transition $transition did not match expected state=$state"
+ }
logD("startAnimation transition=%s", transition)
animateResize(
targetTaskId = state.taskId,
info = info,
startTransaction = startTransaction,
finishTransaction = finishTransaction,
- finishCallback = finishCallback
+ finishCallback = {
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ clearState()
+ },
)
return true
}
@@ -242,12 +271,18 @@ class DesktopImmersiveController(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
) {
logD("animateResize for task#%d", targetTaskId)
- val change = info.changes.first { c ->
+ val change = info.changes.firstOrNull { c ->
val taskInfo = c.taskInfo
- return@first taskInfo != null && taskInfo.taskId == targetTaskId
+ return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+ }
+ if (change == null) {
+ logD("Did not find change for task#%d to animate", targetTaskId)
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ return
}
animateResizeChange(change, startTransaction, finishTransaction, finishCallback)
}
@@ -288,7 +323,6 @@ class DesktopImmersiveController(
.apply()
onTaskResizeAnimationListener?.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null /* wct */)
- clearState()
}
)
addUpdateListener { animation ->
@@ -357,8 +391,17 @@ class DesktopImmersiveController(
// Check if this is a direct immersive enter/exit transition.
if (transition == state?.transition) {
val state = requireState()
- val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId }
- .startAbsBounds
+ val immersiveChange = info.changes.firstOrNull { c ->
+ c.taskInfo?.taskId == state.taskId
+ }
+ if (immersiveChange == null) {
+ logV(
+ "Direct move for task#%d in %s direction missing immersive change.",
+ state.taskId, state.direction
+ )
+ return
+ }
+ val startBounds = immersiveChange.startAbsBounds
logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
when (state.direction) {
Direction.ENTER -> {
@@ -446,11 +489,30 @@ class DesktopImmersiveController(
private fun requireState(): TransitionState =
state ?: error("Expected non-null transition state")
+ private fun getRunningTransitions(): List<IBinder> {
+ val running = mutableListOf<IBinder>()
+ state?.let {
+ running.add(it.transition)
+ }
+ pendingExternalExitTransitions.forEach {
+ running.add(it.transition)
+ }
+ return running
+ }
+
private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
changes.any { c -> c.taskInfo?.taskId == taskId }
+ private fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopImmersiveController")
+ pw.println(innerPrefix + "state=" + state)
+ pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions)
+ }
+
/** The state of the currently running transition. */
- private data class TransitionState(
+ @VisibleForTesting
+ data class TransitionState(
val transition: IBinder,
val displayId: Int,
val taskId: Int,
@@ -483,7 +545,8 @@ class DesktopImmersiveController(
fun asExit(): Exit? = if (this is Exit) this else null
}
- private enum class Direction {
+ @VisibleForTesting
+ enum class Direction {
ENTER, EXIT
}
@@ -495,9 +558,10 @@ class DesktopImmersiveController(
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
- private companion object {
+ companion object {
private const val TAG = "DesktopImmersive"
- private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+ @VisibleForTesting
+ const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 48bb2a8b4a74..2001f9743094 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -52,6 +52,7 @@ class DesktopMixedTransitionHandler(
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
+ private val desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
shellInit: ShellInit,
@@ -161,6 +162,14 @@ class DesktopMixedTransitionHandler(
finishTransaction,
finishCallback
)
+ is PendingMixedTransition.Minimize -> animateMinimizeTransition(
+ pending,
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
}
}
@@ -213,7 +222,12 @@ class DesktopMixedTransitionHandler(
findDesktopTaskChange(info, minimizingTask)
}
val launchChange = findDesktopTaskChange(info, pending.launchingTask)
- ?: error("Should have pending launching task change")
+ if (launchChange == null) {
+ check(minimizeChange == null)
+ check(immersiveExitChange == null)
+ logV("No launch Change, returning")
+ return false
+ }
var subAnimationCount = -1
var combinedWct: WindowContainerTransaction? = null
@@ -267,6 +281,42 @@ class DesktopMixedTransitionHandler(
)
}
+ private fun animateMinimizeTransition(
+ pending: PendingMixedTransition.Minimize,
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback,
+ ): Boolean {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false
+
+ val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask)
+ if (minimizeChange == null) {
+ logW("Should have minimizing desktop task")
+ return false
+ }
+ if (pending.isLastTask) {
+ // Dispatch close desktop task animation to the default transition handlers.
+ return dispatchToLeftoverHandler(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
+ }
+
+ // Animate minimizing desktop task transition with [DesktopBackNavigationTransitionHandler].
+ return desktopBackNavigationTransitionHandler.startAnimation(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ }
+
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
@@ -395,6 +445,14 @@ class DesktopMixedTransitionHandler(
val minimizingTask: Int?,
val exitingImmersiveTask: Int?,
) : PendingMixedTransition()
+
+ /** A task is minimizing. This should be used for task going to back and some closing cases
+ * with back navigation. */
+ data class Minimize(
+ override val transition: IBinder,
+ val minimizingTask: Int,
+ val isLastTask: Boolean,
+ ) : PendingMixedTransition()
}
private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index bed484c7a532..39586e39fdd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -594,6 +594,10 @@ class DesktopModeEventLogger {
FrameworkStatsLog
.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER
),
+ DRAG_TO_TOP_RESIZE_TRIGGER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_TO_TOP_RESIZE_TRIGGER
+ ),
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index edcc877ef58e..c7cf31081c8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean {
}
// Then check if the activity is portrait when letterboxed
- appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+ appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped
// Then check if the activity is portrait
appBounds != null -> appBounds.height() > appBounds.width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index fda709a4a2d7..08ca55f93e3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -102,6 +102,9 @@ class DesktopRepository (
/* Tracks last bounds of task before toggled to stable bounds. */
private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
+ /* Tracks last bounds of task before it is minimized. */
+ private val boundsBeforeMinimizeByTaskId = SparseArray<Rect>()
+
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
@@ -462,6 +465,14 @@ class DesktopRepository (
fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ /** Removes and returns the bounds saved before minimizing the given task. */
+ fun removeBoundsBeforeMinimize(taskId: Int): Rect? =
+ boundsBeforeMinimizeByTaskId.removeReturnOld(taskId)
+
+ /** Saves the bounds of the given task before minimizing. */
+ fun saveBoundsBeforeMinimize(taskId: Int, bounds: Rect?) =
+ boundsBeforeMinimizeByTaskId.set(taskId, Rect(bounds))
+
/** Removes and returns the bounds saved before entering immersive with the given task. */
fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? =
boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId)
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 162879c97a16..6928c255edde 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
@@ -42,6 +42,7 @@ import android.os.Binder
import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
+import android.os.UserHandle
import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
@@ -871,11 +872,10 @@ class DesktopTasksController(
return
}
- // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top.
desktopModeEventLogger.logTaskResizingStarted(
- ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
)
- toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent)
+ toggleDesktopTaskSize(taskInfo, ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent)
}
private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -1175,7 +1175,11 @@ class DesktopTasksController(
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
- val intent = Intent(context, DesktopWallpaperActivity::class.java)
+ val userHandle = UserHandle.of(userId)
+ val userContext =
+ context.createContextAsUser(userHandle, /* flags= */ 0)
+ val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
@@ -1183,11 +1187,13 @@ class DesktopTasksController(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val pendingIntent =
- PendingIntent.getActivity(
- context,
- /* requestCode = */ 0,
+ PendingIntent.getActivityAsUser(
+ userContext,
+ /* requestCode= */ 0,
intent,
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
+ /* bundle= */ null,
+ userHandle
)
wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
}
@@ -1291,7 +1297,11 @@ class DesktopTasksController(
// Check if freeform task launch during recents should be handled
shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
- TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
+ TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
+ task,
+ transition,
+ request.type
+ )
// Check if the top task shouldn't be allowed to enter desktop mode
isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -1621,7 +1631,7 @@ class DesktopTasksController(
}
/** Handle task closing by removing wallpaper activity if it's the last active task */
- private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
+ private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? {
logV("handleTaskClosing")
if (!isDesktopModeShowing(task.displayId))
return null
@@ -1637,8 +1647,15 @@ class DesktopTasksController(
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
taskRepository.addClosingTask(task.displayId, task.taskId)
desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+ } else if (requestType == TRANSIT_CLOSE) {
+ // Handle closing tasks, tasks that are going to back are handled in
+ // [DesktopTasksTransitionObserver].
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1
+ )
+ )
}
-
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
@@ -1770,9 +1787,13 @@ class DesktopTasksController(
transition: IBinder,
taskIdToMinimize: Int,
) {
- val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
+ val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
+ taskId = taskIdToMinimize
+ )
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f0e3a2bd8ffc..77af627a948a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -92,6 +92,12 @@ class DesktopTasksLimiter (
}
taskToMinimize.transitionInfo = info
activeTransitionTokensAndTasks[transition] = taskToMinimize
+
+ // Save current bounds before minimizing in case we need to restore to it later.
+ val boundsBeforeMinimize = info.changes.find { change ->
+ change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+ taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
+
this@DesktopTasksLimiter.minimizeTask(
taskToMinimize.displayId, taskToMinimize.taskId)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index d1534da9a078..c39c715e685c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -46,6 +46,7 @@ class DesktopTasksTransitionObserver(
private val desktopRepository: DesktopRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
shellInit: ShellInit
) : Transitions.TransitionObserver {
@@ -71,7 +72,7 @@ class DesktopTasksTransitionObserver(
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
- handleBackNavigation(info)
+ handleBackNavigation(transition, info)
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
@@ -95,7 +96,7 @@ class DesktopTasksTransitionObserver(
}
}
- private fun handleBackNavigation(info: TransitionInfo) {
+ private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
// When default back navigation happens, transition type is TO_BACK and the change is
// TO_BACK. Mark the task going to back as minimized.
if (info.type == TRANSIT_TO_BACK) {
@@ -105,10 +106,14 @@ class DesktopTasksTransitionObserver(
continue
}
- if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
+ if (visibleTaskCount > 0 &&
change.mode == TRANSIT_TO_BACK &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, taskInfo.taskId, visibleTaskCount == 1))
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index 1c2415c236ad..e835b2fec232 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -36,7 +36,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
class DesktopWallpaperActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index dcbdfa349687..a611fe1db2ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.draganddrop;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
@@ -94,25 +95,30 @@ public class DragSession {
void updateRunningTask() {
final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
final List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
- false /* filterOnlyVisibleRecents */);
- if (!tasks.isEmpty()) {
- for (int i = tasks.size() - 1; i >= 0; i--) {
- final ActivityManager.RunningTaskInfo task = tasks.get(i);
- if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Skipping running task: id=%d component=%s", task.taskId,
- task.baseIntent != null ? task.baseIntent.getComponent() : "null");
- continue;
- }
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
+ mActivityTaskManager.getTasks(5, false /* filterOnlyVisibleRecents */);
+ for (int i = 0; i < tasks.size(); i++) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Running task: id=%d component=%s", task.taskId,
+ "Skipping running task: id=%d component=%s", task.taskId,
task.baseIntent != null ? task.baseIntent.getComponent() : "null");
- break;
+ continue;
}
+ if (!task.isVisible) {
+ // Skip invisible tasks
+ continue;
+ }
+ if (task.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Skip always-on-top floating tasks
+ continue;
+ }
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8d2011d0934..b618bf1215ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -53,6 +53,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -72,6 +73,9 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
public class KeyguardTransitionHandler
implements Transitions.TransitionHandler, KeyguardChangeListener,
TaskStackListenerCallback {
+ private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+ Flags.ensureKeyguardDoesTransitionStarting();
+
private static final String TAG = "KeyguardTransition";
private final Transitions mTransitions;
@@ -194,7 +198,7 @@ public class KeyguardTransitionHandler
// Occlude/unocclude animations are only played if the keyguard is locked.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+ if (isKeyguardOccluding(info)) {
if (hasOpeningDream(info)) {
return startAnimation(mOccludeByDreamTransition, "occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
@@ -202,7 +206,7 @@ public class KeyguardTransitionHandler
return startAnimation(mOccludeTransition, "occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ } else if (isKeyguardUnoccluding(info)) {
return startAnimation(mUnoccludeTransition, "unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
@@ -325,6 +329,36 @@ public class KeyguardTransitionHandler
return false;
}
+ private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) {
+ if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0;
+ }
+
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+ && change.getMode() == TRANSIT_TO_FRONT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) {
+ if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0;
+ }
+
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void finishAnimationImmediately(IBinder transition, StartedTransition playing) {
final IBinder fakeTransition = new Binder();
final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428f1693..0154d0455e50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -45,12 +45,17 @@ public interface Pip {
}
/**
- * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
+ * Set the callback when isInPip state is changed.
*
- * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
- * when it's changed.
+ * @param callback The callback accepts the state of isInPip when it's changed.
*/
- default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
+ default void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
+
+ /**
+ * Remove the callback when isInPip state is changed.
+ * @param callback The callback accepts the state of isInPip when it's changed.
+ */
+ default void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
/**
* Called when showing Pip menu.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4aeecbec7dfb..5276d9d6a4df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -27,6 +27,7 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -176,12 +177,12 @@ public class PipAnimationController {
public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, float startingAngle,
- @Surface.Rotation int rotationDelta) {
+ @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
endBounds, sourceHintRect, direction, 0 /* startingAngle */,
- rotationDelta));
+ rotationDelta, alwaysAnimateTaskBounds));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -197,7 +198,8 @@ public class PipAnimationController {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
- endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
+ endBounds, sourceHintRect, direction, startingAngle, rotationDelta,
+ alwaysAnimateTaskBounds));
}
return mCurrentAnimator;
}
@@ -585,28 +587,32 @@ public class PipAnimationController {
static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint,
@PipAnimationController.TransitionDirection int direction, float startingAngle,
- @Surface.Rotation int rotationDelta) {
+ @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
final boolean isOutPipDirection = isOutPipDirection(direction);
final boolean isInPipDirection = isInPipDirection(direction);
// Just for simplicity we'll interpolate between the source rect hint insets and empty
// insets to calculate the window crop
final Rect initialSourceValue;
final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
- final boolean hasNonMatchFrame = mainWindowFrame != null;
+ final AppCompatTaskInfo compatInfo = taskInfo.appCompatTaskInfo;
+ final boolean isSizeCompatOrLetterboxed = compatInfo.isTopActivityInSizeCompat()
+ || compatInfo.isTopActivityLetterboxed();
+ // For the animation to swipe PIP to home or restore a PIP task from home, we don't
+ // override to the main window frame since we should animate the whole task.
+ final boolean shouldUseMainWindowFrame = mainWindowFrame != null
+ && !alwaysAnimateTaskBounds && !isSizeCompatOrLetterboxed;
final boolean changeOrientation =
rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
final Rect baseBounds = new Rect(baseValue);
final Rect startBounds = new Rect(startValue);
final Rect endBounds = new Rect(endValue);
if (isOutPipDirection) {
- // TODO(b/356277166): handle rotation change with activity that provides main window
- // frame.
- if (hasNonMatchFrame && !changeOrientation) {
+ if (shouldUseMainWindowFrame && !changeOrientation) {
endBounds.set(mainWindowFrame);
}
initialSourceValue = new Rect(endBounds);
} else if (isInPipDirection) {
- if (hasNonMatchFrame) {
+ if (shouldUseMainWindowFrame) {
baseBounds.set(mainWindowFrame);
if (startValue.equals(baseValue)) {
// If the start value is at initial state as in PIP animation, also override
@@ -635,9 +641,19 @@ public class PipAnimationController {
if (changeOrientation) {
lastEndRect = new Rect(endBounds);
rotatedEndRect = new Rect(endBounds);
- // Rotate the end bounds according to the rotation delta because the display will
- // be rotated to the same orientation.
- rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+ // TODO(b/375977163): polish the animation to restoring the PIP task back from
+ // swipe-pip-to-home. Ideally we should send the transitionInfo after reparenting
+ // the PIP activity back to the original task.
+ if (shouldUseMainWindowFrame) {
+ // If we should animate the main window frame, set it to the rotatedRect
+ // instead. The end bounds reported by transitionInfo is the bounds before
+ // rotation, while main window frame is calculated after the rotation.
+ rotatedEndRect.set(mainWindowFrame);
+ } else {
+ // Rotate the end bounds according to the rotation delta because the display
+ // will be rotated to the same orientation.
+ rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+ }
// Use the rect that has the same orientation as the hint rect.
initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c4e63dfdade9..30f1948efa2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -67,6 +68,7 @@ import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -74,7 +76,9 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
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.ScreenshotUtils;
@@ -87,6 +91,7 @@ import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -145,6 +150,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@Nullable private final PipPerfHintController mPipPerfHintController;
+ private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -388,6 +395,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -414,6 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mDesktopRepositoryOptional = desktopRepositoryOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -741,10 +752,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/** Returns the bounds to restore to when exiting PIP mode. */
+ // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
public Rect getExitDestinationBounds() {
+ if (isPipLaunchedInDesktopMode()) {
+ final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+ mTaskInfo.taskId);
+ return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+ }
return mPipBoundsState.getDisplayBounds();
}
+ /** Returns whether PiP was launched while in desktop mode. */
+ // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+ private boolean isPipLaunchedInDesktopMode() {
+ return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
+ && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+ }
+
private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
mTaskOrganizer.applyTransaction(wct);
@@ -1808,7 +1832,25 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* and can be overridden to restore to an alternate windowing mode.
*/
public int getOutPipWindowingMode() {
- // By default, simply reset the windowing mode to undefined.
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mTaskInfo.displayId);
+
+ // If PiP was launched while in desktop mode (we should return the task to freeform
+ // windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+ if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
+ final int displayWindowingMode =
+ tdaInfo.configuration.windowConfiguration.getWindowingMode();
+ if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
return WINDOWING_MODE_UNDEFINED;
}
@@ -1838,9 +1880,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
? mPipBoundsState.getBounds() : currentBounds;
final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
&& mPipAnimationController.getCurrentAnimator().isRunning();
+ // For resize animation, we always animate the whole PIP task bounds.
final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
- sourceHintRect, direction, startingAngle, rotationDelta);
+ sourceHintRect, direction, startingAngle, rotationDelta,
+ true /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(direction)
.setPipTransactionHandler(mPipTransactionHandler)
.setDuration(durationMs);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 28b91c6cb812..f7aed4401247 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -530,6 +530,13 @@ public class PipTransition extends PipTransitionController {
if (mFixedRotationState != FIXED_ROTATION_TRANSITION
&& mFinishTransaction != null) {
mFinishTransaction.merge(tx);
+ // Set window crop and position to destination bounds to avoid flickering.
+ if (hasValidLeash) {
+ mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
+ destinationBounds.height());
+ mFinishTransaction.setPosition(leash, destinationBounds.left,
+ destinationBounds.top);
+ }
}
} else {
wct = new WindowContainerTransaction();
@@ -884,7 +891,8 @@ public class PipTransition extends PipTransitionController {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
- 0 /* startingAngle */, pipRotateDelta);
+ 0 /* startingAngle */, pipRotateDelta,
+ false /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
@@ -899,7 +907,7 @@ public class PipTransition extends PipTransitionController {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
- 0 /* startingAngle */, rotationDelta);
+ 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setDuration(mEnterExitAnimationDuration);
if (startTransaction != null) {
@@ -1095,8 +1103,6 @@ public class PipTransition extends PipTransitionController {
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
- // TODO(b/356277166): add support to swipe PIP to home with
- // non-match parent activity.
handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
sourceHintRect, destinationBounds, taskInfo);
return;
@@ -1118,7 +1124,7 @@ public class PipTransition extends PipTransitionController {
if (enterAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, rotationDelta);
+ 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation. We also temporarily disallow app icon overlay and use color overlay
@@ -1241,10 +1247,14 @@ public class PipTransition extends PipTransitionController {
// to avoid flicker.
final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
pipTaskInfo.displayCutoutInsets.setEmpty();
+ // Always use the task bounds even if the PIP activity doesn't match parent because the app
+ // and the whole task will move behind. We should animate the whole task bounds in this
+ // case.
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */,
+ true /* alwaysAnimateTaskBounds */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 5ffc64f412f1..79a9ce5212c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,7 +47,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
-import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.DefaultMixedHandler;
@@ -312,10 +311,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/** Whether a particular package is same as current pip package. */
- public boolean isPackageActiveInPip(String packageName) {
- final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
- return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
- && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+ public boolean isPackageActiveInPip(@Nullable String packageName) {
+ return packageName != null
+ && mPipBoundsState.getLastPipComponentName() != null
+ && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
}
/** Add PiP-related changes to `outWCT` for the given request. */
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 7f6118689dad..588b88753eb9 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
@@ -104,6 +104,7 @@ import com.android.wm.shell.sysui.UserChangeListener;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -215,7 +216,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private boolean mIsKeyguardShowingOrAnimating;
- private Consumer<Boolean> mOnIsInPipStateChangedListener;
+ private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
@VisibleForTesting
interface PipAnimationListener {
@@ -501,11 +502,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
false /* saveRestoreSnapFraction */);
});
mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> {
- if (mOnIsInPipStateChangedListener != null) {
- final boolean wasInPip = PipTransitionState.isInPip(oldState);
- final boolean nowInPip = PipTransitionState.isInPip(newState);
- if (nowInPip != wasInPip) {
- mOnIsInPipStateChangedListener.accept(nowInPip);
+ final boolean wasInPip = PipTransitionState.isInPip(oldState);
+ final boolean nowInPip = PipTransitionState.isInPip(newState);
+ if (nowInPip != wasInPip) {
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(nowInPip);
}
}
});
@@ -960,13 +961,19 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
}
- private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
- mOnIsInPipStateChangedListener = callback;
- if (mOnIsInPipStateChangedListener != null) {
+ private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.add(callback);
callback.accept(mPipTransitionState.isInPip());
}
}
+ private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.remove(callback);
+ }
+ }
+
private void setShelfHeightLocked(boolean visible, int height) {
final int shelfHeight = visible ? height : 0;
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
@@ -1222,9 +1229,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.addOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
- PipController.this.setOnIsInPipStateChangedListener(callback);
+ PipController.this.removeOnIsInPipStateChangedListener(callback);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 614ef2ab9831..fcba46108f67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -21,6 +21,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
+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.ShellExecutor;
@@ -61,6 +62,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -68,8 +70,9 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenOptional, pipPerfHintControllerOptional, Optional.empty(),
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
mTvPipTransition = tvPipTransition;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
index 24077a35d41c..026482004d51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -24,7 +24,6 @@ import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
-import com.android.wm.shell.transition.Transitions;
/**
* Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
@@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper {
// destination are different.
final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
final Rect crop = mTmpDestinationRect;
- crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
- : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+ crop.set(0, 0, destW, destH);
// Inverse scale for crop to fit in screen coordinates.
crop.scale(1 / scale);
crop.offset(insets.left, insets.top);
@@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper {
}
}
mTmpTransform.setScale(scale, scale);
- mTmpTransform.postRotate(degrees);
mTmpTransform.postTranslate(positionX, positionY);
+ mTmpTransform.postRotate(degrees);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
return this;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 3f9b0c30e314..fb1aba399585 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.pip2.animation;
+import static android.view.Surface.ROTATION_90;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
@@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator {
mAnimationStartCallback.run();
}
if (mStartTransaction != null) {
+ onExpandAnimationUpdate(mStartTransaction, 0f);
mStartTransaction.apply();
}
}
@@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mFinishTransaction != null) {
- // finishTransaction might override some state (eg. corner radii) so we want to
- // manually set the state to the end of the animation
- mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash,
- mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f),
- false /* isInPipDirection */, 1f)
- .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
- .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
+ onExpandAnimationUpdate(mFinishTransaction, 1f);
}
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
@@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
-
- // TODO (b/350801661): implement fixed rotation
- Rect insets = getInsets(fraction);
- mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect,
- insets, false /* isInPipDirection */, fraction)
- .round(tx, mLeash, false /* applyCornerRadius */)
- .shadow(tx, mLeash, false /* applyCornerRadius */);
+ onExpandAnimationUpdate(tx, fraction);
tx.apply();
}
};
@@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator {
mAnimationEndCallback = runnable;
}
+ private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) {
+ Rect insets = getInsets(fraction);
+ if (mRotation == Surface.ROTATION_0) {
+ mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds,
+ mAnimatedRect, insets, false /* isInPipDirection */, fraction);
+ } else {
+ // Fixed rotation case.
+ Rect start = mStartBounds;
+ Rect end = mEndBounds;
+ float degrees, x, y;
+ x = fraction * (end.left - start.left) + start.left;
+ y = fraction * (end.top - start.top) + start.top;
+
+ if (mRotation == ROTATION_90) {
+ degrees = 90 * fraction;
+ } else {
+ degrees = -90 * fraction;
+ }
+ mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds,
+ mAnimatedRect, insets, degrees, x, y,
+ true /* isExpanding */, mRotation == ROTATION_90);
+ }
+ mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */)
+ .shadow(tx, mLeash, false /* applyShadowRadius */);
+ }
+
private Rect getInsets(float fraction) {
final Rect startInsets = mSourceRectHintInsets;
final Rect endInsets = mZeroInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 012dabbbb9f8..4558a9f141c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -30,6 +30,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.shared.animation.Interpolators;
/**
* Animator that handles any resize related animation for PIP.
@@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator {
mRectEvaluator = new RectEvaluator(mAnimatedRect);
setObjectValues(startBounds, endBounds);
+ setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
addListener(mAnimatorListener);
addUpdateListener(mAnimatorUpdateListener);
setEvaluator(mRectEvaluator);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 58d2a8577d8c..44900ce1db8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -573,7 +573,7 @@ public class PhonePipMenuController implements PipMenuController,
@PipTransitionState.TransitionState int newState, Bundle extra) {
switch (newState) {
case PipTransitionState.ENTERED_PIP:
- attach(mPipTransitionState.mPinnedTaskLeash);
+ attach(mPipTransitionState.getPinnedTaskLeash());
break;
case PipTransitionState.EXITED_PIP:
detach();
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
index 9a93371621c9..e901c39b8792 100644
--- 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
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
@@ -66,6 +67,8 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -94,7 +97,7 @@ public class PipController implements ConfigurationChangeListener,
private final PipTouchHandler mPipTouchHandler;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
- private Consumer<Boolean> mOnIsInPipStateChangedListener;
+ private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
private PipAnimationListener mPipRecentsAnimationListener;
@@ -319,7 +322,7 @@ public class PipController implements ConfigurationChangeListener,
mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
mPipBoundsState.setBounds(toBounds);
}
- t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds());
+ t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
}
private void setDisplayLayout(DisplayLayout layout) {
@@ -376,18 +379,20 @@ public class PipController implements ConfigurationChangeListener,
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
- if (visible) {
- Rect rect = new Rect(
- 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
- mPipDisplayLayoutState.getDisplayBounds().right,
- mPipDisplayLayoutState.getDisplayBounds().bottom);
- mPipBoundsState.setNamedUnrestrictedKeepClearArea(
- PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
- } else {
- mPipBoundsState.setNamedUnrestrictedKeepClearArea(
- PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
- }
- mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
+ mPipDisplayLayoutState.getDisplayBounds().right,
+ mPipDisplayLayoutState.getDisplayBounds().bottom);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
+ } else {
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
+ }
+ mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+ });
}
@Override
@@ -411,13 +416,13 @@ public class PipController implements ConfigurationChangeListener,
if (mPipTransitionState.isInSwipePipToHomeTransition()) {
mPipTransitionState.resetSwipePipToHomeState();
}
- if (mOnIsInPipStateChangedListener != null) {
- mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(true /* inPip */);
}
break;
case PipTransitionState.EXITED_PIP:
- if (mOnIsInPipStateChangedListener != null) {
- mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(false /* inPip */);
}
break;
}
@@ -449,13 +454,19 @@ public class PipController implements ConfigurationChangeListener,
mPipTransitionState.dump(pw, innerPrefix);
}
- private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
- mOnIsInPipStateChangedListener = callback;
- if (mOnIsInPipStateChangedListener != null) {
+ private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.add(callback);
callback.accept(mPipTransitionState.isInPip());
}
}
+ private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.remove(callback);
+ }
+ }
+
private void setLauncherAppIconSize(int iconSizePx) {
mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
}
@@ -471,9 +482,16 @@ public class PipController implements ConfigurationChangeListener,
public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
@Override
- public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.addOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
- PipController.this.setOnIsInPipStateChangedListener(callback);
+ PipController.this.removeOnIsInPipStateChangedListener(callback);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 17392bc521d8..3738353dd0a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,7 +785,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private void handleFlingTransition(SurfaceControl.Transaction startTx,
SurfaceControl.Transaction finishTx, Rect destinationBounds) {
- startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+ startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
destinationBounds.left, destinationBounds.top);
startTx.apply();
@@ -799,7 +799,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private void startResizeAnimation(SurfaceControl.Transaction startTx,
SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
- SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
Preconditions.checkState(pipLeash != null,
"No leash cached by mPipTransitionState=" + mPipTransitionState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 751175f0f3e9..d98be55f28e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -531,7 +531,7 @@ public class PipResizeGestureHandler implements
// If resize transition was scheduled from this component, handle leash updates.
mWaitingForBoundsChangeTransition = false;
- SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
Preconditions.checkState(pipLeash != null,
"No leash cached by mPipTransitionState=" + mPipTransitionState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 8b25b11e3a47..5438a014af00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -25,10 +25,13 @@ import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,11 +51,13 @@ public class PipScheduler {
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
private PipTransitionController mPipTransitionController;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@Nullable private Runnable mUpdateMovementBoundsRunnable;
+ private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier;
+
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
@@ -64,10 +69,7 @@ public class PipScheduler {
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
- }
-
- ShellExecutor getMainExecutor() {
- return mMainExecutor;
+ mPipAlphaAnimatorSupplier = PipAlphaAnimator::new;
}
void setPipTransitionController(PipTransitionController pipTransitionController) {
@@ -76,27 +78,29 @@ public class PipScheduler {
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTransitionState.mPipTaskToken == null) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
// final expanded bounds to be inherited from the parent
- wct.setBounds(mPipTransitionState.mPipTaskToken, null);
+ wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
return wct;
}
@Nullable
private WindowContainerTransaction getRemovePipTransaction() {
- if (mPipTransitionState.mPipTaskToken == null) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTransitionState.mPipTaskToken, null);
- wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.reorder(mPipTransitionState.mPipTaskToken, false);
+ wct.setBounds(pipTaskToken, null);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(pipTaskToken, false);
return wct;
}
@@ -117,8 +121,8 @@ public class PipScheduler {
/** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */
public void removePipAfterAnimation() {
SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- PipAlphaAnimator animator = new PipAlphaAnimator(mContext,
- mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT);
+ PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext,
+ mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
animator.start();
}
@@ -159,13 +163,14 @@ public class PipScheduler {
* for running the animator will get this as an extra.
*/
public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
- if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ wct.setBounds(pipTaskToken, toBounds);
if (configAtEnd) {
- wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
+ wct.deferConfigToTransitionEnd(pipTaskToken);
}
mPipTransitionController.startResizeTransition(wct, duration);
}
@@ -203,8 +208,8 @@ public class PipScheduler {
"%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
return;
}
- SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
- final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
+ final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@@ -218,7 +223,7 @@ public class PipScheduler {
tx.apply();
}
- void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) {
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
}
@@ -235,4 +240,23 @@ public class PipScheduler {
mPipBoundsState.setBounds(newBounds);
maybeUpdateMovementBounds();
}
+
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
+
+ @VisibleForTesting
+ interface PipAlphaAnimatorSupplier {
+ PipAlphaAnimator get(@NonNull Context context,
+ SurfaceControl leash,
+ SurfaceControl.Transaction tx,
+ @PipAlphaAnimator.Fade int direction);
+ }
+
+ @VisibleForTesting
+ void setPipAlphaAnimatorSupplier(@NonNull PipAlphaAnimatorSupplier supplier) {
+ mPipAlphaAnimatorSupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 2c7584af1f07..2f9371536a16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -29,6 +29,8 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
@@ -36,6 +38,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.util.ArrayList;
@@ -49,7 +52,8 @@ import java.util.List;
public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
PipTransitionState.PipTransitionStateChangedListener {
private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
- private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+ @VisibleForTesting
+ static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
private final Context mContext;
private final PipTransitionState mPipTransitionState;
@@ -63,6 +67,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
private boolean mWaitingForAspectRatioChange = false;
private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>();
+ private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier;
+
public PipTaskListener(Context context,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
@@ -84,6 +90,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
});
}
+ mPipResizeAnimatorSupplier = PipResizeAnimator::new;
}
void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
@@ -121,6 +128,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
if (mPictureInPictureParams.equals(params)) {
return;
}
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
+ taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
setPictureInPictureParams(params);
float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
@@ -167,18 +177,18 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
- Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+ Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(),
"Leash is null for bounds transition.");
if (mWaitingForAspectRatioChange) {
- PipResizeAnimator animator = new PipResizeAnimator(mContext,
- mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+ mWaitingForAspectRatioChange = false;
+ PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext,
+ mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx,
destinationBounds,
mPipBoundsState.getBounds(), destinationBounds, duration,
0f /* delta */);
- animator.setAnimationEndCallback(() -> {
- mPipScheduler.scheduleFinishResizePip(destinationBounds);
- });
+ animator.setAnimationEndCallback(
+ () -> mPipScheduler.scheduleFinishResizePip(destinationBounds));
animator.start();
}
break;
@@ -192,4 +202,22 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
}
}
+
+ @VisibleForTesting
+ interface PipResizeAnimatorSupplier {
+ PipResizeAnimator get(@NonNull Context context,
+ @NonNull SurfaceControl leash,
+ @Nullable SurfaceControl.Transaction startTx,
+ @Nullable SurfaceControl.Transaction finishTx,
+ @NonNull Rect baseBounds,
+ @NonNull Rect startBounds,
+ @NonNull Rect endBounds,
+ int duration,
+ float delta);
+ }
+
+ @VisibleForTesting
+ void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) {
+ mPipResizeAnimatorSupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 19d293e829ad..65972fb7df48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// KCA triggered movement to wait for other transitions (e.g. due to IME changes).
return;
}
- mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
- boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
- || mPipBoundsState.hasUserResizedPip());
- int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
- - mPipBoundsState.getBounds().top;
+ boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+ || mPipBoundsState.hasUserResizedPip());
+ int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+ - mPipBoundsState.getBounds().top;
- if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
- // If the user hasn't interacted with PiP, we respect the keep clear areas
- mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
- }
- });
+ if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
+ // If the user hasn't interacted with PiP, we respect the keep clear areas
+ mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+ }
};
if (PipUtils.isPip2ExperimentEnabled()) {
@@ -877,7 +875,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMovementWithinDismiss = touchState.getDownTouchPosition().y
>= mPipBoundsState.getMovementBounds().bottom;
mMotionHelper.setSpringingToTouch(false);
- mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash);
+ mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash());
// If the menu is still visible then just poke the menu
// so that it will timeout after the user stops touching it
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 6bf92f69cfb6..d415c10b0cf8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -230,6 +231,11 @@ public class PipTransition extends PipTransitionController implements
// If there is no PiP change, exit this transition handler and potentially try others.
if (pipChange == null) return false;
+ // Other targets might have default transforms applied that are not relevant when
+ // playing PiP transitions, so reset those transforms if needed.
+ prepareOtherTargetTransforms(info, startTransaction, finishTransaction);
+
+ // Update the PipTransitionState while supplying the PiP leash and token to be cached.
Bundle extra = new Bundle();
extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
@@ -325,9 +331,7 @@ public class PipTransition extends PipTransitionController implements
return false;
}
- SurfaceControl pipLeash = pipChange.getLeash();
- Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
-
+ final SurfaceControl pipLeash = getLeash(pipChange);
final Rect destinationBounds = pipChange.getEndAbsBounds();
final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
if (swipePipToHomeOverlay != null) {
@@ -343,17 +347,21 @@ public class PipTransition extends PipTransitionController implements
(destinationBounds.height() - overlaySize) / 2f);
}
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = mPipDisplayLayoutState.getRotation();
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange);
+ pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint);
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, destinationBounds, delta);
@@ -389,55 +397,37 @@ public class PipTransition extends PipTransitionController implements
return false;
}
+ final SurfaceControl pipLeash = getLeash(pipChange);
final Rect startBounds = pipChange.getStartAbsBounds();
final Rect endBounds = pipChange.getEndAbsBounds();
-
final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
- final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
- final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
- endBounds);
- final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
- : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
-
- final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
-
- // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
- // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
- // by the Transitions framework to simplify Task opening transitions.
- if (TransitionUtil.isOpeningType(info.getType())) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getLeash() == null) continue;
- if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- startTransaction.setAlpha(change.getLeash(), 1f);
- }
- }
- }
-
- final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- int startRotation = pipChange.getStartRotation();
- int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
+ final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
+ pipActivityChange);
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsTypeFixedRotation(pipChange, pipActivityChange,
- fixedRotationChange.getEndFixedRotation());
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, delta);
- if (sourceRectHint == null) {
- // update the src-rect-hint in params in place, to set up initial animator transform.
- params.getSourceRectHint().set(adjustedSourceRectHint);
+ if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) {
+ // If app provided src-rect-hint is invalid, use app icon overlay.
animator.setAppIconContentOverlay(
mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ params.copyOnlySet(new PictureInPictureParams.Builder()
+ .setSourceRectHint(adjustedSourceRectHint).build());
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
animator.setAnimationEndCallback(() -> {
if (animator.getContentOverlayLeash() != null) {
@@ -459,11 +449,22 @@ public class PipTransition extends PipTransitionController implements
animator.start();
}
- private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
- TransitionInfo.Change pipActivityChange, int endRotation) {
- final Rect endBounds = pipTaskChange.getEndAbsBounds();
- final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
- int startRotation = pipTaskChange.getStartRotation();
+ private void handleBoundsEnterFixedRotation(TransitionInfo info,
+ TransitionInfo.Change outPipTaskChange,
+ TransitionInfo.Change outPipActivityChange) {
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+ final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds();
+ int startRotation = outPipTaskChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+
+ if (startRotation == endRotation) {
+ return;
+ }
+
+ // This is used by display change listeners to respond properly to fixed rotation.
+ mPipTransitionState.setInFixedRotation(true);
// Cache the task to activity offset to potentially restore later.
Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left,
@@ -492,6 +493,26 @@ public class PipTransition extends PipTransitionController implements
endBounds.top + activityEndOffset.y);
}
+ private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+ final int width = endBounds.width();
+ final int height = endBounds.height();
+ final int left = endBounds.left;
+ final int top = endBounds.top;
+ int newTop, newLeft;
+
+ if (delta == Surface.ROTATION_90) {
+ newLeft = top;
+ newTop = -(left + width);
+ } else {
+ newLeft = -(height + top);
+ newTop = left;
+ }
+ // Modify the endBounds, rotating and placing them potentially off-screen, so that
+ // as we translate and rotate around the origin, we place them right into the target.
+ endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
+ }
+
private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -503,7 +524,7 @@ public class PipTransition extends PipTransitionController implements
}
Rect destinationBounds = pipChange.getEndAbsBounds();
- SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");
// Start transition with 0 alpha at the entry bounds.
@@ -523,7 +544,7 @@ public class PipTransition extends PipTransitionController implements
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- WindowContainerToken pipToken = mPipTransitionState.mPipTaskToken;
+ WindowContainerToken pipToken = mPipTransitionState.getPipTaskToken();
TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
if (pipChange == null) {
@@ -544,33 +565,47 @@ public class PipTransition extends PipTransitionController implements
}
}
- // for multi activity, we need to manually set the leash layer
- if (pipChange.getTaskInfo() == null) {
- TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
- if (parent != null) {
- startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
- }
+ // The parent change if we were in a multi-activity PiP; null if single activity PiP.
+ final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
+ ? getChangeByToken(info, pipChange.getParent()) : null;
+ if (parentBeforePip != null) {
+ // For multi activity, we need to manually set the leash layer
+ startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
}
- Rect startBounds = pipChange.getStartAbsBounds();
- Rect endBounds = pipChange.getEndAbsBounds();
- SurfaceControl pipLeash = pipChange.getLeash();
- Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+ final Rect startBounds = pipChange.getStartAbsBounds();
+ final Rect endBounds = pipChange.getEndAbsBounds();
+ final SurfaceControl pipLeash = getLeash(pipChange);
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
+ PictureInPictureParams params = null;
+ if (pipChange.getTaskInfo() != null) {
// single activity
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
- } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+ params = pipChange.getTaskInfo().pictureInPictureParams;
+ } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
// multi activity
- sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+ params = parentBeforePip.getTaskInfo().pictureInPictureParams;
+ }
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
+ startBounds);
+
+ // We define delta = startRotation - endRotation, so we need to flip the sign.
+ final int delta = -getFixedRotationDelta(info, pipChange);
+ if (delta != ROTATION_0) {
+ // Update PiP target change in place to prepare for fixed rotation;
+ handleExpandFixedRotation(pipChange, delta);
}
PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
- sourceRectHint, Surface.ROTATION_0);
- animator.setAnimationEndCallback(this::finishTransition);
+ sourceRectHint, delta);
+ animator.setAnimationEndCallback(() -> {
+ if (parentBeforePip != null) {
+ // TODO b/377362511: Animate local leash instead to also handle letterbox case.
+ // For multi-activity, set the crop to be null
+ finishTransaction.setCrop(pipLeash, null);
+ }
+ finishTransition();
+ });
animator.start();
return true;
}
@@ -625,6 +660,72 @@ public class PipTransition extends PipTransitionController implements
return null;
}
+ @NonNull
+ private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipTaskChange,
+ @NonNull TransitionInfo.Change pipActivityChange) {
+ final Rect startBounds = pipTaskChange.getStartAbsBounds();
+ final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams;
+
+ // Get the source-rect-hint provided by the app and check its validity; null if invalid.
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
+ endBounds);
+
+ final Rect adjustedSourceRectHint = new Rect();
+ if (sourceRectHint != null) {
+ adjustedSourceRectHint.set(sourceRectHint);
+ // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts;
+ // then, offset the valid app provided source rect hint by the cutout insets.
+ // For single-activity PiP, just use the pinned task to get the cutouts instead.
+ TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null
+ ? getChangeByToken(info, pipActivityChange.getLastParent()) : null;
+ Rect cutoutInsets = parentBeforePip != null
+ ? parentBeforePip.getTaskInfo().displayCutoutInsets
+ : pipTaskChange.getTaskInfo().displayCutoutInsets;
+ if (cutoutInsets != null
+ && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
+ adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
+ }
+ } else {
+ // For non-valid app provided src-rect-hint, calculate one to crop into during
+ // app icon overlay animation.
+ float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
+ adjustedSourceRectHint.set(
+ PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio));
+ }
+ return adjustedSourceRectHint;
+ }
+
+ @Surface.Rotation
+ private int getFixedRotationDelta(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipChange) {
+ TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ int startRotation = pipChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+ int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : startRotation - endRotation;
+ return delta;
+ }
+
+ private void prepareOtherTargetTransforms(TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
+ // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
+ // by the Transitions framework to simplify Task opening transitions.
+ if (TransitionUtil.isOpeningType(info.getType())) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getLeash() == null) continue;
+ if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+ startTransaction.setAlpha(change.getLeash(), 1f);
+ }
+ }
+ }
+
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
@@ -673,11 +774,11 @@ public class PipTransition extends PipTransitionController implements
}
private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
- if (mPipTransitionState.mPipTaskToken == null) {
+ if (mPipTransitionState.getPipTaskToken() == null) {
// PiP removal makes sense if enter-PiP has cached a valid pinned task token.
return false;
}
- TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
+ TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.getPipTaskToken());
if (pipChange == null) {
// Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
return false;
@@ -717,6 +818,13 @@ public class PipTransition extends PipTransitionController implements
}
}
+ @NonNull
+ private SurfaceControl getLeash(TransitionInfo.Change change) {
+ SurfaceControl leash = change.getLeash();
+ Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+ return leash;
+ }
+
//
// Miscellaneous callbacks and listeners
//
@@ -752,19 +860,19 @@ public class PipTransition extends PipTransitionController implements
Preconditions.checkState(extra != null,
"No extra bundle for " + mPipTransitionState);
- mPipTransitionState.mPipTaskToken = extra.getParcelable(
- PIP_TASK_TOKEN, WindowContainerToken.class);
- mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
- PIP_TASK_LEASH, SurfaceControl.class);
- boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
- && mPipTransitionState.mPinnedTaskLeash != null;
+ mPipTransitionState.setPipTaskToken(extra.getParcelable(
+ PIP_TASK_TOKEN, WindowContainerToken.class));
+ mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
+ PIP_TASK_LEASH, SurfaceControl.class));
+ boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
+ && mPipTransitionState.getPinnedTaskLeash() != null;
Preconditions.checkState(hasValidTokenAndLeash,
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- mPipTransitionState.mPipTaskToken = null;
- mPipTransitionState.mPinnedTaskLeash = null;
+ mPipTransitionState.setPipTaskToken(null);
+ mPipTransitionState.setPinnedTaskLeash(null);
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index ccdd66b5d1a8..8e90bfee2636 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -138,11 +138,11 @@ public class PipTransitionState {
// pinned PiP task's WC token
@Nullable
- WindowContainerToken mPipTaskToken;
+ private WindowContainerToken mPipTaskToken;
// pinned PiP task's leash
@Nullable
- SurfaceControl mPinnedTaskLeash;
+ private SurfaceControl mPinnedTaskLeash;
// Overlay leash potentially used during swipe PiP to home transition;
// if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
@@ -304,6 +304,22 @@ public class PipTransitionState {
mSwipePipToHomeAppBounds.setEmpty();
}
+ @Nullable WindowContainerToken getPipTaskToken() {
+ return mPipTaskToken;
+ }
+
+ public void setPipTaskToken(@Nullable WindowContainerToken token) {
+ mPipTaskToken = token;
+ }
+
+ @Nullable SurfaceControl getPinnedTaskLeash() {
+ return mPinnedTaskLeash;
+ }
+
+ void setPinnedTaskLeash(@Nullable SurfaceControl leash) {
+ mPinnedTaskLeash = leash;
+ }
+
/**
* @return true if either in swipe or button-nav fixed rotation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index f739d65e63c3..49cf8ae81aa8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -63,6 +63,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
"Bubbles"),
WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_COMPAT_UI),
+ WM_SHELL_APP_COMPAT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_APP_COMPAT),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
@@ -131,6 +133,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
private static final String TAG_WM_COMPAT_UI = "CompatUi";
+ private static final String TAG_WM_APP_COMPAT = "AppCompat";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 799028a5507a..4a301cc0b603 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@ import android.os.Bundle;
import com.android.wm.shell.recents.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
/**
* Interface that is exposed to remote callers to fetch recent tasks.
@@ -44,7 +44,7 @@ interface IRecentTasks {
/**
* Gets the set of recent tasks.
*/
- GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
+ GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
/**
* Gets the set of running tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 371bdd5c6469..b58f0681c571 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -18,6 +18,8 @@ package com.android.wm.shell.recents;
import android.app.ActivityManager.RunningTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
+
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
*/
@@ -44,8 +46,8 @@ oneway interface IRecentTasksListener {
void onRunningTaskChanged(in RunningTaskInfo taskInfo);
/** A task has moved to front. */
- oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+ void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks);
/** A task info has changed. */
- oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
+ void onTaskInfoChanged(in RunningTaskInfo taskInfo);
} \ No newline at end of file
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 8c5d1e7e069d..364a087211c5 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
@@ -19,7 +19,7 @@ package com.android.wm.shell.recents;
import android.annotation.Nullable;
import android.graphics.Color;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.List;
@@ -35,7 +35,7 @@ public interface RecentTasks {
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
- Consumer<List<GroupedRecentTaskInfo>> callback) {
+ Consumer<List<GroupedTaskInfo>> callback) {
}
/**
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 faa20159f64a..9911669d2cb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R
import android.Manifest;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.KeyguardManager;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
return;
}
try {
- mListener.onTaskMovedToFront(taskInfo);
+ GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+ mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask });
} catch (RemoteException e) {
Slog.w(TAG, "Failed call onTaskMovedToFront", e);
}
@@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@VisibleForTesting
- ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
+ ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is from the most-recent to least-recent order
- final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+ final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
maxNum, flags, userId);
// Make a mapping of task id -> task info
- final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
+ final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
for (int i = 0; i < rawList.size(); i++) {
- final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+ final TaskInfo taskInfo = rawList.get(i);
rawMapping.put(taskInfo.taskId, taskInfo);
}
- ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+ ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
Set<Integer> minimizedFreeformTasks = new HashSet<>();
int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
// Pull out the pairs as we iterate back in the list
- ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
+ ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>();
for (int i = 0; i < rawList.size(); i++) {
- final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+ final RecentTaskInfo taskInfo = rawList.get(i);
if (!rawMapping.contains(taskInfo.taskId)) {
// If it's not in the mapping, then it was already paired with another task
continue;
@@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
pairedTaskId)) {
- final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
+ final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
rawMapping.remove(pairedTaskId);
- recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
mTaskSplitBoundsMap.get(pairedTaskId)));
} else {
- recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo));
+ recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
recentTasks.add(mostRecentFreeformTaskIndex,
- GroupedRecentTaskInfo.forFreeformTasks(
- freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]),
+ GroupedTaskInfo.forFreeformTasks(
+ freeformTasks,
minimizedFreeformTasks));
}
@@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
* {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
- public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+ public RecentTaskInfo findTaskInBackground(ComponentName componentName,
int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
- List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+ List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
ActivityManager.getCurrentUser());
for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RecentTaskInfo task = tasks.get(i);
+ final RecentTaskInfo task = tasks.get(i);
if (task.isVisible) {
continue;
}
@@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
* Find the background task that match the given taskId.
*/
@Nullable
- public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
- List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+ public RecentTaskInfo findTaskInBackground(int taskId) {
+ List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
ActivityManager.getCurrentUser());
for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RecentTaskInfo task = tasks.get(i);
+ final RecentTaskInfo task = tasks.get(i);
if (task.isVisible) {
continue;
}
@@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
pw.println(prefix + TAG);
pw.println(prefix + " mListener=" + mListener);
pw.println(prefix + "Tasks:");
- ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
+ ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
pw.println(innerPrefix + recentTasks.get(i));
@@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
private class RecentTasksImpl implements RecentTasks {
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
- Consumer<List<GroupedRecentTaskInfo>> callback) {
+ Consumer<List<GroupedTaskInfo>> callback) {
mMainExecutor.execute(() -> {
- List<GroupedRecentTaskInfo> tasks =
+ List<GroupedTaskInfo> tasks =
RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
executor.execute(() -> callback.accept(tasks));
});
@@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) {
mListener.call(l -> l.onTaskMovedToFront(taskInfo));
}
@@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
+ public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
throws RemoteException {
if (mController == null) {
// The controller is already invalidated -- just return an empty task list for now
- return new GroupedRecentTaskInfo[0];
+ return new GroupedTaskInfo[0];
}
- final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
+ final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
- (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
- .toArray(new GroupedRecentTaskInfo[0]),
+ (controller) -> {
+ List<GroupedTaskInfo> tasks = controller.getRecentTasks(
+ maxNum, flags, userId);
+ out[0] = tasks.toArray(new GroupedTaskInfo[0]);
+ },
true /* blocking */);
return out[0];
}
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 40065b9287a6..417a6558ffcc 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
@@ -34,6 +34,8 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -67,6 +69,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
@@ -216,8 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
break;
}
}
- final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
- mixer == null ? this : mixer);
+ final int transitionType = Flags.enableShellTopTaskTracking()
+ ? TRANSIT_START_RECENTS_TRANSITION
+ : TRANSIT_TO_FRONT;
+ final IBinder transition = mTransitions.startTransition(transitionType,
+ wct, mixer == null ? this : mixer);
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
@@ -300,7 +306,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- controller.merge(info, t, finishCallback);
+ controller.merge(info, t, mergeTarget, finishCallback);
}
@Override
@@ -367,6 +373,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
private boolean mPausingSeparateHome = false;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ // This is the transition that backs the entire recents transition, and the one that the
+ // pending finish transition below will be merged into
private IBinder mTransition = null;
private boolean mKeyguardLocked = false;
private boolean mWillFinishToHome = false;
@@ -386,6 +394,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// next called.
private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
+ // Used to track a pending finish transition
+ private IBinder mPendingFinishTransition;
+ private IResultReceiver mPendingRunnerFinishCb;
+
RecentsController(IRecentsAnimationRunner listener) {
mInstanceId = System.identityHashCode(this);
mListener = listener;
@@ -523,6 +535,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mInfo = null;
mTransition = null;
mPendingPauseSnapshotsForCancel = null;
+ mPipTaskId = -1;
+ mPipTask = null;
+ mPipTransaction = null;
+ mPendingRunnerFinishCb = null;
+ mPendingFinishTransition = null;
mControllers.remove(this);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onAnimationStateChanged(false);
@@ -734,6 +751,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// the pausing apps.
t.setLayer(target.leash, layer);
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " not handling home taskId=%d", taskInfo.taskId);
// do nothing
} else if (TransitionUtil.isOpeningType(change.getMode())) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -872,16 +891,35 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
}
+ /**
+ * Note: because we use a book-end transition to finish the recents transition, we must
+ * either always merge the incoming transition, or always cancel the recents transition
+ * if we don't handle the incoming transition to ensure that the end transition is queued
+ * before any unhandled transitions.
+ */
@SuppressLint("NewApi")
- void merge(TransitionInfo info, SurfaceControl.Transaction t,
+ void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
if (mFinishCB == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: skip, no finish callback",
mInstanceId);
- // This was no-op'd (likely a repeated start) and we've already sent finish.
+ // This was no-op'd (likely a repeated start) and we've already completed finish.
return;
}
+
+ if (Flags.enableShellTopTaskTracking()
+ && info.getType() == TRANSIT_END_RECENTS_TRANSITION
+ && mergeTarget == mTransition) {
+ // This is a pending finish, so merge the end transition to trigger completing the
+ // cleanup of the recents transition
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
+ mInstanceId);
+ finishCallback.onTransitionFinished(null /* wct */);
+ return;
+ }
+
if (info.getType() == TRANSIT_SLEEP) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: transit_sleep", mInstanceId);
@@ -1245,8 +1283,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
return;
}
- if (mFinishCB == null) {
+ if (mFinishCB == null
+ || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
Slog.e(TAG, "Duplicate call to finish");
+ 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);
+ }
+ }
return;
}
@@ -1254,19 +1303,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
&& !mWillFinishToHome
&& mPausingTasks != null
&& mState == STATE_NORMAL;
- if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
- mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
- } else if (!toHome) {
- // For some transitions, we may have notified home activity that it became visible.
- // We need to notify the observer that we are no longer going home.
- mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ if (!Flags.enableShellTopTaskTracking()) {
+ // This is only necessary when the recents transition is finished using a finishWCT,
+ // otherwise a new transition will notify the relevant observers
+ if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+ } else if (!toHome) {
+ // For some transitions, we may have notified home activity that it became
+ // visible. We need to notify the observer that we are no longer going home.
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ }
}
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
- + "willFinishToHome=%b state=%d",
- mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState);
- final Transitions.TransitionFinishCallback finishCB = mFinishCB;
- mFinishCB = null;
+ + "willFinishToHome=%b state=%d reason=%s",
+ mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState, reason);
final SurfaceControl.Transaction t = mFinishTransaction;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1328,6 +1380,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
for (int i = 0; i < mClosingTasks.size(); ++i) {
cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
}
+
if (mPipTransaction != null && sendUserLeaveHint) {
SurfaceControl pipLeash = null;
TransitionInfo.Change pipChange = null;
@@ -1379,15 +1432,50 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */);
// We need to clear the WCT to send finishWCT=null for Recents.
wct.clear();
+
+ if (Flags.enableShellTopTaskTracking()) {
+ // In this case, we've already started the PIP transition, so we can
+ // clean up immediately
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(null);
+ return;
+ }
}
}
- mPipTaskId = -1;
- mPipTask = null;
- mPipTransaction = null;
}
}
+
+ if (Flags.enableShellTopTaskTracking()) {
+ if (!wct.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: "
+ + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
+ mPendingRunnerFinishCb = runnerFinishCb;
+ mPendingFinishTransition = mTransitions.startTransition(
+ TRANSIT_END_RECENTS_TRANSITION, wct,
+ new PendingFinishTransitionHandler());
+ } else {
+ // If there's no work to do, just go ahead and clean up
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(null /* wct */);
+ }
+ } else {
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(wct);
+ }
+ }
+
+ /**
+ * Runs the actual logic to finish the recents transition.
+ */
+ private void onFinishInner(@Nullable WindowContainerTransaction wct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: Completing finish", mInstanceId);
+ final Transitions.TransitionFinishCallback finishCb = mFinishCB;
+ final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb;
+
cleanUp();
- finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
+ finishCb.onTransitionFinished(wct);
if (runnerFinishCb != null) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1472,6 +1560,40 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
});
}
+
+ /**
+ * A temporary transition handler used with the pending finish transition, which runs the
+ * cleanup/finish logic once the pending transition is merged/handled.
+ * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+ */
+ private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ // Once we have merged (or not if the WCT didn't result in any changes), then we can
+ // run the pending finish logic
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.onTransitionConsumed: "
+ + "Consumed pending finish transition", mInstanceId);
+ onFinishInner(null /* wct */);
+ }
+ };
};
/** Utility class to track the state of a task as-seen by recents. */
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 ec58292b352c..9fcf98b9efc2 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
@@ -353,7 +353,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
boolean isSeamlessDisplayChange = false;
if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
- if (info.getType() == TRANSIT_CHANGE) {
+ if (info.getType() == TRANSIT_CHANGE || isOnlyTranslucent) {
final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
@@ -395,10 +395,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
// No default animation for this, so just update bounds/position.
- final int rootIdx = TransitionUtil.rootIndexFor(change, info);
- startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
- change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
+ if (change.getParent() == null) {
+ // For independent change without a parent, we have reparented it to the root
+ // leash in Transitions#setupAnimHierarchy.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ startTransaction.setPosition(change.getLeash(),
+ change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
+ } else {
+ startTransaction.setPosition(change.getLeash(),
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
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 1d456aed5f4d..3f191497e1ed 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
@@ -203,6 +203,12 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition type to minimize a task. */
public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20;
+ /** Transition to start the recents transition */
+ public static final int TRANSIT_START_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 21;
+
+ /** Transition to end the recents transition */
+ public static final int TRANSIT_END_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 22;
+
/** Transition type for desktop mode transitions. */
public static final int TRANSIT_DESKTOP_MODE_TYPES =
WindowManager.TRANSIT_FIRST_CUSTOM + 100;
@@ -1875,6 +1881,8 @@ public class Transitions implements RemoteCallable<Transitions>,
case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
case TRANSIT_MINIMIZE -> "MINIMIZE";
+ case TRANSIT_START_RECENTS_TRANSITION -> "START_RECENTS_TRANSITION";
+ case TRANSIT_END_RECENTS_TRANSITION -> "END_RECENTS_TRANSITION";
default -> "";
};
return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index be4fd7c5eeec..c9f2d2e8c0e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -24,6 +24,9 @@ import static android.content.pm.PackageManager.FEATURE_PC;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ContentResolver;
import android.content.Context;
@@ -108,6 +111,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
}
mMainExecutor.execute(() -> {
mExclusionRegion.set(systemGestureExclusion);
+ onExclusionRegionChanged(displayId, mExclusionRegion);
});
}
};
@@ -161,7 +165,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
boolean isFocusedGlobally) {
final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null) {
- decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion);
}
}
@@ -195,7 +199,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
return;
}
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+ if (enableDisplayFocusInShellTransitions()) {
+ // Pass the current global focus status to avoid updates outside of a ShellTransition.
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+ } else {
+ decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
+ }
}
@Override
@@ -233,7 +242,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
}
@@ -247,7 +256,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
@Override
@@ -259,6 +268,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
decoration.close();
}
+ private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) {
+ final int decorCount = mWindowDecorByTaskId.size();
+ for (int i = 0; i < decorCount; i++) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration.mTaskInfo.displayId != displayId) continue;
+ decoration.onExclusionRegionChanged(exclusionRegion);
+ }
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return true;
@@ -326,7 +344,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
private class CaptionTouchEventListener implements
@@ -496,4 +514,4 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
return Settings.Global.getInt(resolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
}
-} \ No newline at end of file
+}
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 c9546731a193..982fda0ddf36 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
@@ -36,6 +36,7 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.util.Size;
@@ -174,7 +175,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
@Override
- void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
+ void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -186,7 +188,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
}
@VisibleForTesting
@@ -198,7 +200,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
InsetsState displayInsetsState,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region globalExclusionRegion) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
@@ -210,6 +213,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
+ relayoutParams.mDisplayExclusionRegion.set(globalExclusionRegion);
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// If the app is requesting to customize the caption bar, allow input to fall
@@ -236,7 +240,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region globalExclusionRegion) {
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue()
@@ -249,7 +254,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ globalExclusionRegion);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 17e3dd2fc68e..d71e61a4c4de 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
@@ -31,6 +31,7 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
@@ -219,6 +220,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
mMainExecutor.execute(() -> {
mExclusionRegion.set(systemGestureExclusion);
+ onExclusionRegionChanged(displayId, mExclusionRegion);
});
}
};
@@ -431,7 +433,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
boolean isFocusedGlobally) {
final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null) {
- decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion);
}
}
@@ -464,11 +466,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
if (taskInfo.displayId != oldTaskInfo.displayId
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+ if (enableDisplayFocusInShellTransitions()) {
+ // Pass the current global focus status to avoid updates outside of a ShellTransition.
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+ } else {
+ decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
+ }
mActivityOrientationChangeHandler.ifPresent(handler ->
handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
}
@@ -508,7 +515,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
}
}
@@ -522,7 +530,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
}
@Override
@@ -533,7 +542,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.close();
final int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
removeTaskFromEventReceiver(displayId);
}
// Remove the decoration from the cache last because WindowDecoration#close could still
@@ -542,6 +551,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
+ private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) {
+ final int decorCount = mWindowDecorByTaskId.size();
+ for (int i = 0; i < decorCount; i++) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration.mTaskInfo.displayId != displayId) continue;
+ decoration.onExclusionRegionChanged(exclusionRegion);
+ }
+ }
+
private void openHandleMenu(int taskId) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo)
@@ -744,10 +762,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
/**
* Whether to pilfer the next motion event to send cancellations to the windows below.
- * Useful when the caption window is spy and the gesture should be handle by the system
+ * Useful when the caption window is spy and the gesture should be handled by the system
* instead of by the app for their custom header content.
+ * Should not have any effect when {@link Flags#enableAccessibleCustomHeaders()}, because
+ * a spy window is not used then.
*/
- private boolean mShouldPilferCaptionEvents;
+ private boolean mIsCustomHeaderGesture;
+ private boolean mIsResizeGesture;
private boolean mIsDragging;
private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
@@ -761,7 +782,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback;
final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- final long appHandleHoldToDragDuration = Flags.enableHoldToDragAppHandle()
+ final long appHandleHoldToDragDuration =
+ DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue()
? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0;
mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration,
touchSlop);
@@ -861,7 +883,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// to offset position relative to caption as a whole.
int[] viewLocation = new int[2];
v.getLocationInWindow(viewLocation);
- final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e,
+ mIsResizeGesture = decoration.shouldResizeListenerHandleEvent(e,
new Point(viewLocation[0], viewLocation[1]));
// The caption window may be a spy window when the caption background is
// transparent, which means events will fall through to the app window. Make
@@ -869,21 +891,23 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// customizable region and what the app reported as exclusion areas, because
// the drag-move or other caption gestures should take priority outside those
// regions.
- mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
- && downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
+ mIsCustomHeaderGesture = downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption;
}
- if (!mShouldPilferCaptionEvents) {
- // The event will be handled by a window below or pilfered by resize handler.
+ if (mIsCustomHeaderGesture || mIsResizeGesture) {
+ // The event will be handled by the custom window below or pilfered by resize
+ // handler.
return false;
}
- // Otherwise pilfer so that windows below receive cancellations for this gesture, and
- // continue normal handling as a caption gesture.
- if (mInputManager != null) {
+ if (mInputManager != null
+ && !Flags.enableAccessibleCustomHeaders()) {
+ // Pilfer so that windows below receive cancellations for this gesture.
mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
}
if (isUpOrCancel) {
// Gesture is finished, reset state.
- mShouldPilferCaptionEvents = false;
+ mIsCustomHeaderGesture = false;
+ mIsResizeGesture = false;
}
if (isAppHandle) {
return mHandleDragDetector.onMotionEvent(v, e);
@@ -1228,7 +1252,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
relevantDecor.updateHoverAndPressStatus(ev);
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mTransitionDragActive && !Flags.enableHandleInputFix()) {
+ if (!mTransitionDragActive && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
relevantDecor.closeHandleMenuIfNeeded(ev);
}
}
@@ -1271,7 +1295,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
final boolean shouldStartTransitionDrag =
relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
- || Flags.enableHandleInputFix();
+ || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
mTransitionDragActive = true;
}
@@ -1586,8 +1610,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
- if (!Flags.enableHandleInputFix()) {
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
}
@@ -1612,6 +1637,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
+ pw.println(innerPrefix + "mExclusionRegion=" + mExclusionRegion);
}
private class DesktopModeOnTaskRepositionAnimationListener
@@ -1748,7 +1774,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& Flags.enableDesktopWindowingImmersiveHandleHiding()) {
decor.onInsetsStateChanged(insetsState);
}
- if (!Flags.enableHandleInputFix()) {
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// If status bar inset is visible, top task is not in immersive mode.
// This value is only needed when the App Handle input is being handled
// through the global input monitor (hence the flag check) to ignore gestures
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 d97632a9428c..cdcf14e0cbf3 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
@@ -394,7 +394,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
@Override
- void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// The visibility, crop and position of the task should only be set when a task is
// fluid resizing. In all other cases, it is expected that the transition handler sets
@@ -415,7 +416,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus);
+ hasGlobalFocus, displayExclusionRegion);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -442,18 +443,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> show its header in sync since it's an integral part
// of the window itself - a delayed header might cause bad UX.
relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
} else {
// The Task is outside Freeform mode -> allow the handle view to be delayed since the
// handle is just a small addition to the window.
relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
}
Trace.endSection();
}
@@ -462,11 +463,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
if (mResult.mRootView != null) {
updateViewHost(mRelayoutParams, startT, mResult);
}
@@ -489,7 +490,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
if (applyStartTransactionOnDraw) {
throw new IllegalArgumentException(
"We cannot both sync viewhost ondraw and delay viewhost creation.");
@@ -498,7 +500,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus);
+ hasGlobalFocus, displayExclusionRegion);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
@@ -513,7 +515,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
if (Flags.enableDesktopWindowingAppToWeb()) {
setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
@@ -538,7 +540,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded, inFullImmersive,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ displayExclusionRegion);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -628,13 +631,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Nullable
private Intent getBrowserLink() {
- // Do not show browser link in browser applications
- final ComponentName baseActivity = mTaskInfo.baseActivity;
- if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
- baseActivity.getPackageName(), mUserContext.getUserId())) {
- return null;
- }
-
final Uri browserLink;
// If the captured link is available and has not expired, return the captured link.
// Otherwise, return the generic link which is set to null if a generic link is unavailable.
@@ -651,6 +647,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
+ @Nullable
+ private Intent getAppLink() {
+ return mWebUri == null ? null
+ : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager());
+ }
+
+ private boolean isBrowserApp() {
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ return baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
+ baseActivity.getPackageName(), mUserContext.getUserId());
+ }
+
UserHandle getUser() {
return mUserContext.getUser();
}
@@ -807,7 +815,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void disposeStatusBarInputLayer() {
if (!isAppHandle(mWindowDecorViewHolder)
- || !Flags.enableHandleInputFix()) {
+ || !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
return;
}
asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
@@ -874,7 +882,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
@NonNull InsetsState displayInsetsState,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -885,6 +894,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
relayoutParams.mHasGlobalFocus = hasGlobalFocus;
+ relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
final boolean showCaption;
if (Flags.enableFullyImmersiveInDesktop()) {
@@ -910,10 +920,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode;
if (isAppHeader) {
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
- // If the app is requesting to customize the caption bar, allow input to fall
- // through to the windows below so that the app can respond to input events on
- // their custom content.
- relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ // The app is requesting to customize the caption bar, which means input on
+ // customizable/exclusion regions must go to the app instead of to the system.
+ // This may be accomplished with spy windows or custom touchable regions:
+ if (Flags.enableAccessibleCustomHeaders()) {
+ // Set the touchable region of the caption to only the areas where input should
+ // be handled by the system (i.e. non custom-excluded areas). The region will
+ // be calculated based on occluding caption elements and exclusion areas
+ // reported by the app.
+ relayoutParams.mLimitTouchRegionToSystemAreas = true;
+ } else {
+ // Allow input to fall through to the windows below so that the app can respond
+ // to input events on their custom content.
+ relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
} else {
if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) {
// Force-consume the caption bar insets when the app tries to hide the caption.
@@ -951,7 +971,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
- } else if (isAppHandle && !Flags.enableHandleInputFix()) {
+ } else if (isAppHandle && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// The focused decor (fullscreen/split) does not need to handle input because input in
// the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
// Note: This does not apply with the above flag enabled as the status bar input layer
@@ -1368,6 +1388,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.shouldShowChangeAspectRatioButton(mTaskInfo);
final boolean inDesktopImmersive = mDesktopRepository
.isTaskInFullImmersiveState(mTaskInfo.taskId);
+ final boolean isBrowserApp = isBrowserApp();
mHandleMenu = mHandleMenuFactory.create(
this,
mWindowManagerWrapper,
@@ -1379,7 +1400,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
supportsMultiInstance,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- getBrowserLink(),
+ isBrowserApp,
+ isBrowserApp ? getAppLink() : getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX,
@@ -1560,13 +1582,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
- || Flags.enableHandleInputFix()) {
+ || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
return false;
}
// The status bar input layer can only receive input in handle coordinates to begin with,
// so checking coordinates is unnecessary as input is always within handle bounds.
if (isAppHandle(mWindowDecorViewHolder)
- && Flags.enableHandleInputFix()
+ && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()
&& isCaptionVisible()) {
return true;
}
@@ -1603,7 +1625,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare
*/
void checkTouchEvent(MotionEvent ev) {
- if (mResult.mRootView == null || Flags.enableHandleInputFix()) return;
+ if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
@@ -1616,7 +1638,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH.
// This is for the case that some of the handle menu is underneath the status bar.
if (isAppHandle(mWindowDecorViewHolder)
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
mHandleMenu.checkMotionEvent(ev);
closeHandleMenuIfNeeded(ev);
}
@@ -1630,7 +1652,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare against.
*/
void updateHoverAndPressStatus(MotionEvent ev) {
- if (mResult.mRootView == null || Flags.enableHandleInputFix()) return;
+ if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
&& checkTouchEventInFocusedCaptionHandle(ev);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 2edc380756ac..54c247bff984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -39,12 +39,14 @@ import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
+import android.window.DesktopModeFlags
import android.window.SurfaceSyncGroup
+import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.compose.ui.graphics.toArgb
import androidx.core.view.isGone
-import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.apptoweb.isBrowserApp
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -73,7 +75,8 @@ class HandleMenu(
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
private val shouldShowChangeAspectRatioButton: Boolean,
- private val openInBrowserIntent: Intent?,
+ private val isBrowserApp: Boolean,
+ private val openInAppOrBrowserIntent: Intent?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int,
@@ -83,7 +86,7 @@ class HandleMenu(
private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo
private val isViewAboveStatusBar: Boolean
- get() = (Flags.enableHandleInputFix() && !taskInfo.isFreeform)
+ get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform)
private val pillElevation: Int = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_pill_elevation)
@@ -111,7 +114,7 @@ class HandleMenu(
private val globalMenuPosition: Point = Point()
private val shouldShowBrowserPill: Boolean
- get() = openInBrowserIntent != null
+ get() = openInAppOrBrowserIntent != null
private val shouldShowMoreActionsPill: Boolean
get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
@@ -128,7 +131,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
- openInBrowserClickListener: (Intent) -> Unit,
+ openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
@@ -146,7 +149,7 @@ class HandleMenu(
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
- openInBrowserClickListener = openInBrowserClickListener,
+ openInAppOrBrowserClickListener = openInAppOrBrowserClickListener,
onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
onOutsideTouchListener = onOutsideTouchListener,
@@ -167,7 +170,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
- openInBrowserClickListener: (Intent) -> Unit,
+ openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
@@ -181,7 +184,8 @@ class HandleMenu(
shouldShowBrowserPill = shouldShowBrowserPill,
shouldShowNewWindowButton = shouldShowNewWindowButton,
shouldShowManageWindowsButton = shouldShowManageWindowsButton,
- shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
+ shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton,
+ isBrowserApp = isBrowserApp
).apply {
bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
this.onToDesktopClickListener = onToDesktopClickListener
@@ -190,8 +194,8 @@ class HandleMenu(
this.onNewWindowClickListener = onNewWindowClickListener
this.onManageWindowsClickListener = onManageWindowsClickListener
this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
- this.onOpenInBrowserClickListener = {
- openInBrowserClickListener.invoke(openInBrowserIntent!!)
+ this.onOpenInAppOrBrowserClickListener = {
+ openInAppOrBrowserClickListener.invoke(openInAppOrBrowserIntent!!)
}
this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
this.onCloseMenuClickListener = onCloseMenuClickListener
@@ -201,7 +205,8 @@ class HandleMenu(
val x = handleMenuPosition.x.toInt()
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
- if ((!taskInfo.isFreeform && Flags.enableHandleInputFix()) || forceShowSystemBars) {
+ if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue())
+ || forceShowSystemBars) {
AdditionalSystemViewContainer(
windowManagerWrapper = windowManagerWrapper,
taskId = taskInfo.taskId,
@@ -237,7 +242,7 @@ class HandleMenu(
menuX = marginMenuStart
menuY = captionY + marginMenuTop
} else {
- if (Flags.enableHandleInputFix()) {
+ if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// In a focused decor, we use global coordinates for handle menu. Therefore we
// need to account for other factors like split stage and menu/handle width to
// center the menu.
@@ -435,14 +440,15 @@ class HandleMenu(
/** The view within the Handle Menu, with options to change the windowing mode and more. */
@SuppressLint("ClickableViewAccessibility")
class HandleMenuView(
- context: Context,
+ private val context: Context,
menuWidth: Int,
captionHeight: Int,
private val shouldShowWindowingPill: Boolean,
private val shouldShowBrowserPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
- private val shouldShowChangeAspectRatioButton: Boolean
+ private val shouldShowChangeAspectRatioButton: Boolean,
+ private val isBrowserApp: Boolean
) {
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -472,11 +478,12 @@ class HandleMenu(
private val changeAspectRatioBtn = moreActionsPill
.requireViewById<Button>(R.id.change_aspect_ratio_button)
- // Open in Browser Pill.
- private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
- private val browserBtn = openInBrowserPill.requireViewById<Button>(
- R.id.open_in_browser_button)
- private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>(
+ // Open in Browser/App Pill.
+ private val openInAppOrBrowserPill = rootView.requireViewById<View>(
+ R.id.open_in_app_or_browser_pill)
+ private val openInAppOrBrowserBtn = openInAppOrBrowserPill.requireViewById<Button>(
+ R.id.open_in_app_or_browser_button)
+ private val openByDefaultBtn = openInAppOrBrowserPill.requireViewById<ImageButton>(
R.id.open_by_default_button)
private val decorThemeUtil = DecorThemeUtil(context)
private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
@@ -490,7 +497,7 @@ class HandleMenu(
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
var onChangeAspectRatioClickListener: (() -> Unit)? = null
- var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onOpenInAppOrBrowserClickListener: (() -> Unit)? = null
var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
var onOutsideTouchListener: (() -> Unit)? = null
@@ -499,7 +506,7 @@ class HandleMenu(
fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() }
splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
- browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ openInAppOrBrowserBtn.setOnClickListener { onOpenInAppOrBrowserClickListener?.invoke() }
openByDefaultBtn.setOnClickListener {
onOpenByDefaultClickListener?.invoke()
}
@@ -535,10 +542,10 @@ class HandleMenu(
if (shouldShowMoreActionsPill) {
bindMoreActionsPill(style)
}
- bindOpenInBrowserPill(style)
+ bindOpenInAppOrBrowserPill(style)
}
- /** Animates the menu opening. */
+ /** Animates the menu openInAppOrBrowserg. */
fun animateOpenMenu() {
if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
animator.animateCaptionHandleExpandToOpen()
@@ -660,13 +667,20 @@ class HandleMenu(
}
}
- private fun bindOpenInBrowserPill(style: MenuStyle) {
- openInBrowserPill.apply {
+ private fun bindOpenInAppOrBrowserPill(style: MenuStyle) {
+ openInAppOrBrowserPill.apply {
isGone = !shouldShowBrowserPill
background.setTint(style.backgroundColor)
}
- browserBtn.apply {
+ val btnText = if (isBrowserApp) {
+ getString(R.string.open_in_app_text)
+ } else {
+ getString(R.string.open_in_browser_text)
+ }
+ openInAppOrBrowserBtn.apply {
+ text = btnText
+ contentDescription = btnText
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
@@ -674,6 +688,8 @@ class HandleMenu(
openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
}
+ private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+
private data class MenuStyle(
@ColorInt val backgroundColor: Int,
@ColorInt val textColor: Int,
@@ -708,7 +724,8 @@ interface HandleMenuFactory {
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
- openInBrowserIntent: Intent?,
+ isBrowserApp: Boolean,
+ openInAppOrBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -729,7 +746,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
- openInBrowserIntent: Intent?,
+ isBrowserApp: Boolean,
+ openInAppOrBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -746,7 +764,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowNewWindowButton,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- openInBrowserIntent,
+ isBrowserApp,
+ openInAppOrBrowserIntent,
captionWidth,
captionHeight,
captionX,
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
index 0c475f12f53b..470e5a1d88b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -74,7 +74,8 @@ class HandleMenuAnimator(
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)
- private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
+ private val openInAppOrBrowserPill: ViewGroup =
+ handleMenu.requireViewById(R.id.open_in_app_or_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -83,7 +84,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
- animateOpenInBrowserPill()
+ animateOpenInAppOrBrowserPill()
runAnimations {
appInfoPill.post {
appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
@@ -103,7 +104,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
- animateOpenInBrowserPill()
+ animateOpenInAppOrBrowserPill()
runAnimations {
appInfoPill.post {
appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
@@ -124,7 +125,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
- openInBrowserPillClose()
+ openInAppOrBrowserPillClose()
runAnimations(after)
}
@@ -141,7 +142,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
- openInBrowserPillClose()
+ openInAppOrBrowserPillClose()
runAnimations(after)
}
@@ -154,7 +155,7 @@ class HandleMenuAnimator(
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
- openInBrowserPill.alpha = 0f
+ openInAppOrBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -166,8 +167,8 @@ class HandleMenuAnimator(
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
- openInBrowserPill.pivotX = menuWidth / 2f
- openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
+ openInAppOrBrowserPill.pivotX = menuWidth / 2f
+ openInAppOrBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -297,36 +298,36 @@ class HandleMenuAnimator(
}
}
- private fun animateOpenInBrowserPill() {
+ private fun animateOpenInAppOrBrowserPill() {
// Open in Browser X & Y Scaling Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_OPEN_DELAY
duration = BODY_SCALE_OPEN_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_OPEN_DELAY
duration = BODY_SCALE_OPEN_DURATION
}
// Open in Browser Opacity Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_ALPHA_OPEN_DURATION
}
// Open in Browser Elevation Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Z, 1f).apply {
startDelay = ELEVATION_OPEN_DELAY
duration = BODY_ELEVATION_OPEN_DURATION
}
// Open in Browser Button Opacity Animation
- val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ val button = openInAppOrBrowserPill.requireViewById<Button>(R.id.open_in_app_or_browser_button)
animators +=
ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
@@ -438,33 +439,33 @@ class HandleMenuAnimator(
}
}
- private fun openInBrowserPillClose() {
+ private fun openInAppOrBrowserPillClose() {
// Open in Browser X & Y Scaling Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
duration = BODY_CLOSE_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
duration = BODY_CLOSE_DURATION
}
// Open in Browser Opacity Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply {
duration = BODY_CLOSE_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply {
duration = BODY_CLOSE_DURATION
}
// Upward Open in Browser y-translation Animation
val yStart: Float = -captionHeight / 2
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Y, yStart).apply {
duration = BODY_CLOSE_DURATION
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
index cf82bb4f9919..8bc56e0807a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
@@ -16,13 +16,12 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
-import com.android.window.flags.Flags
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ImageButton
+import android.window.DesktopModeFlags
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
/**
* A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers.
@@ -39,7 +38,7 @@ class HandleMenuImageButton(
lateinit var taskInfo: RunningTaskInfo
override fun onHoverEvent(motionEvent: MotionEvent): Boolean {
- if (Flags.enableHandleInputFix() || taskInfo.isFreeform) {
+ if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() || taskInfo.isFreeform) {
return super.onHoverEvent(motionEvent)
} else {
return false
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 b016c755e323..a3c75bf33cde 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
@@ -127,7 +127,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
mDisplayController.removeDisplayWindowListener(this);
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
};
@@ -143,7 +143,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mDecorationContainerSurface;
SurfaceControl mCaptionContainerSurface;
- private WindowlessWindowManager mCaptionWindowManager;
+ private CaptionWindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
@@ -152,6 +152,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
boolean mIsStatusBarVisible;
boolean mIsKeyguardVisibleAndOccluded;
boolean mHasGlobalFocus;
+ final Region mExclusionRegion = Region.obtain();
/** The most recent set of insets applied to this window decoration. */
private WindowDecorationInsets mWindowDecorationInsets;
@@ -218,7 +219,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* constructor.
* @param hasGlobalFocus Whether the task is focused
*/
- abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus);
+ abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion);
/**
* Used by the {@link DragPositioningCallback} associated with the implementing class to
@@ -244,6 +246,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTaskInfo = params.mRunningTaskInfo;
}
mHasGlobalFocus = params.mHasGlobalFocus;
+ mExclusionRegion.set(params.mDisplayExclusionRegion);
final int oldLayoutResId = mLayoutResId;
mLayoutResId = params.mLayoutResId;
@@ -402,7 +405,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int elementWidthPx =
resources.getDimensionPixelSize(element.mWidthResId);
boundingRects[i] =
- calculateBoundingRect(element, elementWidthPx, captionInsetsRect);
+ calculateBoundingRectLocal(element, elementWidthPx, captionInsetsRect);
// Subtract the regions used by the caption elements, the rest is
// customizable.
if (params.hasInputFeatureSpy()) {
@@ -477,9 +480,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new WindowlessWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface,
- null /* hostInputToken */);
+ mCaptionWindowManager = new CaptionWindowlessWindowManager(
+ mTaskInfo.getConfiguration(), mCaptionContainerSurface);
}
mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
final WindowManager.LayoutParams lp =
@@ -492,6 +494,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
lp.inputFeatures = params.mInputFeatures;
+ final Rect localCaptionBounds = new Rect(
+ outResult.mCaptionX,
+ outResult.mCaptionY,
+ outResult.mCaptionX + outResult.mCaptionWidth,
+ outResult.mCaptionY + outResult.mCaptionHeight);
+ final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
+ ? calculateLimitedTouchableRegion(params, localCaptionBounds)
+ : null;
if (mViewHost == null) {
Trace.beginSection("CaptionViewHostLayout-new");
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
@@ -503,6 +513,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+ if (params.mLimitTouchRegionToSystemAreas) {
+ mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
+ }
mViewHost.setView(outResult.mRootView, lp);
Trace.endSection();
} else {
@@ -514,13 +527,71 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+ if (params.mLimitTouchRegionToSystemAreas) {
+ mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
+ }
mViewHost.relayout(lp);
Trace.endSection();
}
+ if (touchableRegion != null) {
+ touchableRegion.recycle();
+ }
Trace.endSection(); // CaptionViewHostLayout
}
- private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
+ @NonNull
+ private Region calculateLimitedTouchableRegion(
+ RelayoutParams params,
+ @NonNull Rect localCaptionBounds) {
+ // Make caption bounds relative to display to align with exclusion region.
+ final Point positionInParent = params.mRunningTaskInfo.positionInParent;
+ final Rect captionBoundsInDisplay = new Rect(localCaptionBounds);
+ captionBoundsInDisplay.offsetTo(positionInParent.x, positionInParent.y);
+
+ final Region boundingRects = calculateBoundingRectsRegion(params, captionBoundsInDisplay);
+
+ final Region customizedRegion = Region.obtain();
+ customizedRegion.set(captionBoundsInDisplay);
+ customizedRegion.op(boundingRects, Region.Op.DIFFERENCE);
+ customizedRegion.op(params.mDisplayExclusionRegion, Region.Op.INTERSECT);
+
+ final Region touchableRegion = Region.obtain();
+ touchableRegion.set(captionBoundsInDisplay);
+ touchableRegion.op(customizedRegion, Region.Op.DIFFERENCE);
+ // Return resulting region back to window coordinates.
+ touchableRegion.translate(-positionInParent.x, -positionInParent.y);
+
+ boundingRects.recycle();
+ customizedRegion.recycle();
+ return touchableRegion;
+ }
+
+ @NonNull
+ private Region calculateBoundingRectsRegion(
+ @NonNull RelayoutParams params,
+ @NonNull Rect captionBoundsInDisplay) {
+ final int numOfElements = params.mOccludingCaptionElements.size();
+ final Region region = Region.obtain();
+ if (numOfElements == 0) {
+ // The entire caption is a bounding rect.
+ region.set(captionBoundsInDisplay);
+ return region;
+ }
+ final Resources resources = mDecorWindowContext.getResources();
+ for (int i = 0; i < numOfElements; i++) {
+ final OccludingCaptionElement element = params.mOccludingCaptionElements.get(i);
+ final int elementWidthPx = resources.getDimensionPixelSize(element.mWidthResId);
+ final Rect boundingRect = calculateBoundingRectLocal(element, elementWidthPx,
+ captionBoundsInDisplay);
+ // Bounding rect is initially calculated relative to the caption, so offset it to make
+ // it relative to the display.
+ boundingRect.offset(captionBoundsInDisplay.left, captionBoundsInDisplay.top);
+ region.union(boundingRect);
+ }
+ return region;
+ }
+
+ private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element,
int elementWidthPx, @NonNull Rect captionRect) {
switch (element.mAlignment) {
case START -> {
@@ -539,7 +610,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mIsKeyguardVisibleAndOccluded = visible && occluded;
final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
if (changed) {
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
}
@@ -549,10 +620,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
if (changed) {
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
}
+ void onExclusionRegionChanged(@NonNull Region exclusionRegion) {
+ relayout(mTaskInfo, mHasGlobalFocus, exclusionRegion);
+ }
+
/**
* Update caption visibility state and views.
*/
@@ -751,9 +826,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mCaptionHeightId;
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
+ boolean mLimitTouchRegionToSystemAreas;
int mInputFeatures;
boolean mIsInsetSource = true;
@InsetsSource.Flags int mInsetSourceFlags;
+ final Region mDisplayExclusionRegion = Region.obtain();
int mShadowRadiusId;
int mCornerRadius;
@@ -772,9 +849,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
+ mLimitTouchRegionToSystemAreas = false;
mInputFeatures = 0;
mIsInsetSource = true;
mInsetSourceFlags = 0;
+ mDisplayExclusionRegion.setEmpty();
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
@@ -830,6 +909,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ private static class CaptionWindowlessWindowManager extends WindowlessWindowManager {
+ CaptionWindowlessWindowManager(
+ @NonNull Configuration configuration,
+ @NonNull SurfaceControl rootSurface) {
+ super(configuration, rootSurface, /* hostInputToken= */ null);
+ }
+
+ /** Set the view host's touchable region. */
+ void setTouchRegion(@NonNull SurfaceControlViewHost viewHost, @NonNull Region region) {
+ setTouchRegion(viewHost.getWindowToken().asBinder(), region);
+ }
+ }
+
@VisibleForTesting
public interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index e43c3a613157..0e40a5350a43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val taskRepository: DesktopRepository,
+ private val desktopModeEventLogger: DesktopModeEventLogger,
) : DisplayChangeController.OnDisplayChangingListener {
@VisibleForTesting
var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
@@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel(
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
taskRepository,
+ desktopModeEventLogger,
)
tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
newHandler
@@ -100,7 +103,7 @@ class DesktopTilingDecorViewModel(
fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean {
return tilingTransitionHandlerByDisplayId
.get(taskInfo.displayId)
- ?.moveTiledPairToFront(taskInfo) ?: false
+ ?.moveTiledPairToFront(taskInfo, isTaskFocused = true) ?: false
}
fun onOverviewAnimationStateChange(isRunning: Boolean) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 209eb5e501b2..6cdc517c9cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.RoundedCorner
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
@@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER
import android.view.WindowlessWindowManager
import com.android.wm.shell.R
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import java.util.function.Supplier
/**
@@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager(
t.setRelativeLayer(leash, relativeLeash, 1)
}
- override fun onDividerMoveStart(pos: Int) {
+ override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) {
setSlippery(false)
+ transitionHandler.onDividerHandleDragStart(motionEvent)
}
/**
@@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager(
* Notifies the transition handler of tiling operations ending, which might result in resizing
* WindowContainerTransactions if the sizes of the tiled tasks changed.
*/
- override fun onDividerMovedEnd(pos: Int) {
+ override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) {
setSlippery(true)
val t = transactionSupplier.get()
t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
val dividerWidth = dividerBounds.width()
dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
- transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
+ transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent)
}
private fun getWindowManagerParams(): WindowManager.LayoutParams {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index c46767c3a51d..418b8ecd5534 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -25,6 +25,7 @@ import android.graphics.Rect
import android.os.IBinder
import android.os.UserHandle
import android.util.Slog
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
@@ -44,6 +45,8 @@ 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.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val taskRepository: DesktopRepository,
+ private val desktopModeEventLogger: DesktopModeEventLogger,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) :
Transitions.TransitionHandler,
@@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration(
return tilingManager
}
+ fun onDividerHandleDragStart(motionEvent: MotionEvent) {
+ val leftTiledTask = leftTaskResizingHelper ?: return
+ val rightTiledTask = rightTaskResizingHelper ?: return
+
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ leftTiledTask.taskInfo,
+ displayController,
+ )
+
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ rightTiledTask.taskInfo,
+ displayController,
+ )
+ }
+
fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean {
val leftTiledTask = leftTaskResizingHelper ?: return false
val rightTiledTask = rightTaskResizingHelper ?: return false
@@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration(
return true
}
- fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) {
+ fun onDividerHandleDragEnd(
+ dividerBounds: Rect,
+ t: SurfaceControl.Transaction,
+ motionEvent: MotionEvent,
+ ) {
val leftTiledTask = leftTaskResizingHelper ?: return
val rightTiledTask = rightTaskResizingHelper ?: return
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ leftTiledTask.taskInfo,
+ leftTiledTask.newBounds.height(),
+ leftTiledTask.newBounds.width(),
+ displayController,
+ )
+
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ rightTiledTask.taskInfo,
+ rightTiledTask.newBounds.height(),
+ rightTiledTask.newBounds.width(),
+ displayController,
+ )
+
if (leftTiledTask.newBounds == leftTiledTask.bounds) {
leftTiledTask.hideVeil()
rightTiledTask.hideVeil()
@@ -426,9 +471,9 @@ class DesktopTilingWindowDecoration(
}
}
+ // Only called if [taskInfo] relates to a focused task
private fun isTilingFocusRemoved(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.isFocused &&
- isTilingFocused &&
+ return isTilingFocused &&
taskInfo.taskId != leftTaskResizingHelper?.taskInfo?.taskId &&
taskInfo.taskId != rightTaskResizingHelper?.taskInfo?.taskId
}
@@ -439,9 +484,9 @@ class DesktopTilingWindowDecoration(
}
}
+ // Only called if [taskInfo] relates to a focused task
private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean {
return !isTilingFocused &&
- taskInfo.isFocused &&
(taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId ||
taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId)
}
@@ -528,9 +573,19 @@ class DesktopTilingWindowDecoration(
removeTaskIfTiled(taskId, taskVanished = true, shouldDelayUpdate = true)
}
- fun moveTiledPairToFront(taskInfo: RunningTaskInfo): Boolean {
+ /**
+ * Moves the tiled pair to the front of the task stack, if the [taskInfo] is focused and one of
+ * the two tiled tasks.
+ *
+ * If specified, [isTaskFocused] will override [RunningTaskInfo.isFocused]. This is to be used
+ * when called when the task will be focused, but the [taskInfo] hasn't been updated yet.
+ */
+ fun moveTiledPairToFront(taskInfo: RunningTaskInfo, isTaskFocused: Boolean? = null): Boolean {
if (!isTilingManagerInitialised) return false
+ val isFocused = isTaskFocused ?: taskInfo.isFocused
+ if (!isFocused) return false
+
// If a task that isn't tiled is being focused, let the generic handler do the work.
if (isTilingFocusRemoved(taskInfo)) {
isTilingFocused = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
index b3b30ad4c09e..9799d01afc9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
@@ -15,14 +15,16 @@
*/
package com.android.wm.shell.windowdecor.tiling
+import android.view.MotionEvent
+
/** Divider move callback to whichever entity that handles the moving logic. */
interface DividerMoveCallback {
/** Called on the divider move start gesture. */
- fun onDividerMoveStart(pos: Int)
+ fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent)
/** Called on the divider moved by dragging it. */
fun onDividerMove(pos: Int): Boolean
/** Called on divider move gesture end. */
- fun onDividerMovedEnd(pos: Int)
+ fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 89229051941c..111e28e450bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (!isWithinHandleRegion(yTouchPosInDivider)) return true
- callback.onDividerMoveStart(touchPos)
+ callback.onDividerMoveStart(touchPos, event)
setTouching()
canResize = true
}
@@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
if (!canResize) return true
if (moving && resized) {
dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
- callback.onDividerMovedEnd(dividerBounds.left)
+ callback.onDividerMovedEnd(dividerBounds.left, event)
}
moving = false
canResize = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index b5700ffb046b..503ad92d4d71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -34,15 +34,14 @@ import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
+import android.window.DesktopModeFlags
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import com.android.internal.policy.SystemBarUtils
-import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
@@ -141,7 +140,7 @@ internal class AppHandleViewHolder(
private fun createStatusBarInputLayer(handlePosition: Point,
handleWidth: Int,
handleHeight: Int) {
- if (!Flags.enableHandleInputFix()) return
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return
statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4fe66f3357a3..4cddf31321d6 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -23,8 +23,9 @@ import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
-import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
@@ -44,6 +45,7 @@ import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTop
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.common.Components.LAUNCHER
import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
@@ -365,5 +367,57 @@ class DesktopModeFlickerScenarios {
AppWindowAlignsWithOnlyOneDisplayCornerAtEnd(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val MINIMIZE_APP =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MINIMIZE_APP"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions
+ .filter { it.type == TransitionType.MINIMIZE }
+ .sortedByDescending { it.id }
+ .drop(1)
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
+ val MINIMIZE_LAST_APP =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MINIMIZE_LAST_APP"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ val lastTransition =
+ transitions
+ .filter { it.type == TransitionType.MINIMIZE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(LAUNCHER),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
new file mode 100644
index 000000000000..58582b02c212
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsLandscape : MinimizeAppWindows(rotation = ROTATION_90) {
+ @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+ @Test
+ override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(MINIMIZE_APP)
+ .use(MINIMIZE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
new file mode 100644
index 000000000000..7970426a6ee8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsPortrait : MinimizeAppWindows() {
+ @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+ @Test
+ override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(MINIMIZE_APP)
+ .use(MINIMIZE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
new file mode 100644
index 000000000000..7e0b81a783c0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MaximizeAppWindowWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MaximizeAppWindowWithDragToTopDragZoneTest : MaximizeAppWindowWithDragToTopDragZone()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 824c4482c1e6..f442fdb31592 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.scenarios
import android.tools.NavBar
import android.tools.Rotation
+import com.android.internal.R
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
import org.junit.After
@@ -40,6 +41,9 @@ constructor(
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ // Skip the test when the drag-to-maximize is enabled on this device.
+ Assume.assumeFalse(Flags.enableDragToMaximize() &&
+ instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
testApp.enterDesktopWithDrag(wmHelper, device)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
new file mode 100644
index 000000000000..a2b88f278ff2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.internal.R
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for maximizing a desktop app window by dragging it to the top drag zone.
+ */
+@Ignore("Test Base Class")
+abstract class MaximizeAppWindowWithDragToTopDragZone
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ // Skip the test when the drag-to-maximize is disabled on this device.
+ Assume.assumeTrue(Flags.enableDragToMaximize() &&
+ instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWithDragToTopDragZone() {
+ testApp.maximizeAppWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 266e48482568..2ed7d07ac75e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -62,6 +62,7 @@ public class BackProgressAnimatorTest {
@Before
public void setUp() throws Exception {
+ mTargetProgressCalled = new CountDownLatch(1);
mMainThreadHandler = new Handler(Looper.getMainLooper());
final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
index d38b848fbb4d..329a10998f23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -16,9 +16,8 @@
package com.android.wm.shell.bubbles.bar;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
-import android.graphics.drawable.ColorDrawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -47,10 +46,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_lightBg() {
mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
}
@@ -58,10 +56,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_darkBg() {
mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
new file mode 100644
index 000000000000..b9490b881d08
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.pip;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipAppOpsListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipAppOpsListenerTest {
+
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPackageManager;
+ @Mock private AppOpsManager mMockAppOpsManager;
+ @Mock private PipAppOpsListener.Callback mMockCallback;
+ @Mock private ShellExecutor mMockExecutor;
+
+ private PipAppOpsListener mPipAppOpsListener;
+
+ private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mOnOpChangedListenerCaptor;
+ private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+ private Pair<ComponentName, Integer> mTopPipActivity;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+ .thenReturn(mMockAppOpsManager);
+ mOnOpChangedListenerCaptor = ArgumentCaptor.forClass(
+ AppOpsManager.OnOpChangedListener.class);
+ mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ }
+
+ @Test
+ public void onActivityPinned_registerAppOpsListener() {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityPinned(packageName);
+
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void onActivityUnpinned_unregisterAppOpsListener() {
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityUnpinned();
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void disablePipAppOps_dismissPip() throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockExecutor).execute(mRunnableArgumentCaptor.capture());
+ Runnable runnable = mRunnableArgumentCaptor.getValue();
+ runnable.run();
+ verify(mMockCallback).dismissPip();
+ }
+
+ @Test
+ public void disablePipAppOps_differentPackage_doNothing()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName + ".modified";
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verifyZeroInteractions(mMockExecutor);
+ }
+
+ @Test
+ public void disablePipAppOps_nameNotFound_unregisterAppOpsListener()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ private Pair<ComponentName, Integer> getTopPipActivity(Context context) {
+ return mTopPipActivity;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
new file mode 100644
index 000000000000..6df8d6fd7717
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WindowingMode
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+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.ShellExecutor
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val closingTaskLeash = mock<SurfaceControl>()
+ private val displayController = mock<DisplayController>()
+
+ private lateinit var handler: DesktopBackNavigationTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler =
+ DesktopBackNavigationTransitionHandler(
+ testExecutor,
+ testExecutor,
+ displayController
+ )
+ whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(handler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_openTransition_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate open transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionFullscreenTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate fullscreen task to back transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionOpeningFreeformTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ changeMode = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate opening freeform task to back transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionToBackFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should animate going to back freeform task close transition", animates)
+ }
+
+ @Test
+ fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(
+ type = TRANSIT_CLOSE,
+ changeMode = TRANSIT_CLOSE,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should animate going to back freeform task close transition", animates)
+ }
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_TO_BACK,
+ changeMode: Int = WindowManager.TRANSIT_TO_BACK,
+ task: RunningTaskInfo
+ ): TransitionInfo =
+ TransitionInfo(type, 0 /* flags */).apply {
+ addChange(
+ TransitionInfo.Change(mock(), closingTaskLeash).apply {
+ mode = changeMode
+ parent = null
+ taskInfo = task
+ }
+ )
+ }
+
+ private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(windowingMode)
+ .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index e05a0b54fcf4..a4f4d05d2079 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
import android.graphics.Rect
@@ -24,6 +25,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.Display.DEFAULT_DISPLAY
import android.view.Surface
import android.view.SurfaceControl
@@ -43,6 +45,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -64,17 +67,19 @@ import org.mockito.kotlin.whenever
* Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest
*/
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DesktopImmersiveControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var mockTransitions: Transitions
private lateinit var desktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayLayout: DisplayLayout
- private val transactionSupplier = { SurfaceControl.Transaction() }
+ private val transactionSupplier = { StubTransaction() }
private lateinit var controller: DesktopImmersiveController
@@ -89,10 +94,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
(invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
}
controller = DesktopImmersiveController(
+ shellInit = mock(),
transitions = mockTransitions,
desktopRepository = desktopRepository,
displayController = mockDisplayController,
shellTaskOrganizer = mockShellTaskOrganizer,
+ shellCommandHandler = mock(),
transactionSupplier = transactionSupplier,
)
}
@@ -672,6 +679,60 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
assertThat(controller.isImmersiveChange(transition, change)).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ controller.moveTaskToNonImmersive(task)
+
+ controller.animateResizeChange(
+ change = TransitionInfo.Change(task.token, SurfaceControl()).apply {
+ taskInfo = task
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = { }
+ )
+ animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
+
+ assertThat(controller.state).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAnimation_missingChange_clearsState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ controller.moveTaskToImmersive(task)
+
+ controller.startAnimation(
+ transition = mockBinder,
+ info = createTransitionInfo(changes = emptyList()),
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = {}
+ )
+
+ assertThat(controller.state).isNull()
+ }
+
private fun createTransitionInfo(
@TransitionType type: Int = TRANSIT_CHANGE,
@TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index df061e368071..f21f26443748 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -32,6 +32,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TransitionType
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -77,16 +78,28 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
- @Mock lateinit var transitions: Transitions
- @Mock lateinit var desktopRepository: DesktopRepository
- @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
- @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
- @Mock lateinit var desktopImmersiveController: DesktopImmersiveController
- @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock lateinit var mockHandler: Handler
- @Mock lateinit var closingTaskLeash: SurfaceControl
- @Mock lateinit var shellInit: ShellInit
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock
+ lateinit var transitions: Transitions
+ @Mock
+ lateinit var desktopRepository: DesktopRepository
+ @Mock
+ lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
+ @Mock
+ lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
+ @Mock
+ lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler
+ @Mock
+ lateinit var desktopImmersiveController: DesktopImmersiveController
+ @Mock
+ lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock
+ lateinit var mockHandler: Handler
+ @Mock
+ lateinit var closingTaskLeash: SurfaceControl
+ @Mock
+ lateinit var shellInit: ShellInit
+ @Mock
+ lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
private lateinit var mixedHandler: DesktopMixedTransitionHandler
@@ -100,6 +113,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController,
+ desktopBackNavigationTransitionHandler,
interactionJankMonitor,
mockHandler,
shellInit,
@@ -447,6 +461,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Launch(
+ transition = transition,
+ launchingTask = launchingTask.taskId,
+ minimizingTask = null,
+ exitingImmersiveTask = null,
+ )
+ )
+
+ val started = mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(nonLaunchTaskChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ assertFalse("Should not start animation without launching desktop task", started)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -564,6 +609,87 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun startAnimation_withMinimizingDesktopTask_callsBackNavigationHandler() {
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+ whenever(
+ desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+ )
+ .thenReturn(true)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Minimize(
+ transition = transition,
+ minimizingTask = minimizingTask.taskId,
+ isLastTask = false,
+ )
+ )
+
+ val minimizingTaskChange = createChange(minimizingTask)
+ val started = mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ TRANSIT_TO_BACK,
+ listOf(minimizingTaskChange)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should delegate animation to back navigation transition handler", started)
+ verify(desktopBackNavigationTransitionHandler)
+ .startAnimation(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun startAnimation_withMinimizingLastDesktopTask_dispatchesTransition() {
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+ whenever(
+ desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+ )
+ .thenReturn(true)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Minimize(
+ transition = transition,
+ minimizingTask = minimizingTask.taskId,
+ isLastTask = true,
+ )
+ )
+
+ val minimizingTaskChange = createChange(minimizingTask)
+ mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ TRANSIT_TO_BACK,
+ listOf(minimizingTaskChange)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler)
+ )
+ }
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_CLOSE,
changeMode: Int = WindowManager.TRANSIT_CLOSE,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 414c1a658b95..7f790d574a7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -936,6 +936,28 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
+ fun saveBoundsBeforeMinimize_boundsSavedByTaskId() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+
+ assertThat(repo.removeBoundsBeforeMinimize(taskId)).isEqualTo(bounds)
+ }
+
+ @Test
+ fun removeBoundsBeforeMinimize_returnsNullAfterBoundsRemoved() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+ repo.removeBoundsBeforeMinimize(taskId)
+
+ val boundsBeforeMinimize = repo.removeBoundsBeforeMinimize(taskId)
+
+ assertThat(boundsBeforeMinimize).isNull()
+ }
+
+ @Test
fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
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 315a46fcbd7b..ad266ead774e 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
@@ -3038,6 +3038,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ // Assert event is properly logged
+ verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ motionEvent,
+ task,
+ displayController
+ )
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ motionEvent,
+ task,
+ STABLE_BOUNDS.height(),
+ STABLE_BOUNDS.width(),
+ displayController
+ )
}
@Test
@@ -3082,6 +3097,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
eq(STABLE_BOUNDS),
anyOrNull(),
)
+ // Assert no event is logged
+ verify(desktopModeEventLogger, never()).logTaskResizingStarted(
+ any(), any(), any(), any(), any()
+ )
+ verify(desktopModeEventLogger, never()).logTaskResizingEnded(
+ any(), any(), any(), any(), any(), any(), any()
+ )
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 01b69aed8465..456b50da095b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
import android.os.Binder
import android.os.Handler
import android.platform.test.annotations.DisableFlags
@@ -24,8 +25,10 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -63,6 +66,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.kotlin.eq
@@ -235,6 +239,30 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ val bounds = Rect(0, 0, 200, 200)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ desktopTasksLimiter.addPendingMinimizeChange(
+ transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+
+ val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+ mode = TRANSIT_TO_BACK
+ taskInfo = task
+ setStartAbsBounds(bounds)
+ }
+ desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ transition,
+ TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+ StubTransaction() /* startTransaction */,
+ StubTransaction() /* finishTransaction */)
+
+ assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
+ assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo(
+ bounds)
+ }
+
+ @Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
val mergedTransition = Binder()
val newTransition = Binder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 737439ce3cfe..7f1c1db3207a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -76,6 +76,7 @@ class DesktopTasksTransitionObserverTest {
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
private val taskRepository = mock<DesktopRepository>()
+ private val mixedHandler = mock<DesktopMixedTransitionHandler>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
@@ -87,7 +88,7 @@ class DesktopTasksTransitionObserverTest {
transitionObserver =
DesktopTasksTransitionObserver(
- context, taskRepository, transitions, shellTaskOrganizer, shellInit
+ context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit
)
}
@@ -106,6 +107,7 @@ class DesktopTasksTransitionObserverTest {
)
verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ verify(mixedHandler).addPendingMixedTransition(any())
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 840126421c08..e40bbad7adda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -20,6 +20,8 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
+import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;
+
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
@@ -30,9 +32,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.os.RemoteException;
import android.view.Display;
import android.view.DragEvent;
@@ -128,7 +128,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
doReturn(display).when(dragLayout).getDisplay();
doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
- final ClipData clipData = createClipData();
+ final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
final DragEvent event = mock(DragEvent.class);
doReturn(ACTION_DRAG_STARTED).when(event).getAction();
doReturn(clipData).when(event).getClipData();
@@ -150,15 +150,4 @@ public class DragAndDropControllerTest extends ShellTestCase {
mController.onDrag(dragLayout, event);
verify(mDragAndDropListener, never()).onDragStarted();
}
-
- private ClipData createClipData() {
- ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT,
- new String[] { MIMETYPE_APPLICATION_SHORTCUT });
- Intent i = new Intent();
- i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg");
- i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId");
- i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
- ClipData.Item item = new ClipData.Item(i);
- return new ClipData(clipDescription, item);
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
new file mode 100644
index 000000000000..3d59342f62d8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.draganddrop
+
+import android.app.ActivityTaskManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ClipDescription
+import android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID
+import android.os.PersistableBundle
+import android.os.RemoteException
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for DragSession.
+ *
+ * Usage: atest WMShellUnitTests:DragSessionTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DragSessionTest : ShellTestCase() {
+ @Mock
+ private lateinit var activityTaskManager: ActivityTaskManager
+
+ @Mock
+ private lateinit var displayLayout: DisplayLayout
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testGetRunningTask() {
+ // Set up running tasks
+ val runningTasks = listOf(
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
+ )
+ whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
+
+ // Simulate dragging an app
+ val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
+
+ // Start a new drag session
+ val session = DragSession(activityTaskManager, displayLayout, data, 0)
+ session.updateRunningTask()
+
+ assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first())
+ assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode)
+ assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType)
+ }
+
+ @Test
+ fun testGetRunningTaskWithFloatingTasks() {
+ // Set up running tasks
+ val runningTasks = listOf(
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
+ createTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, alwaysOnTop=true),
+ )
+ whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
+
+ // Simulate dragging an app
+ val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
+
+ // Start a new drag session
+ val session = DragSession(activityTaskManager, displayLayout, data, 0)
+ session.updateRunningTask()
+
+ // Ensure that we find the first non-floating task
+ assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first())
+ assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode)
+ assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType)
+ }
+
+ @Test
+ fun testHideDragSource_readDragFlag() {
+ // Set up running tasks
+ val runningTasks = listOf(
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
+ )
+ whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
+
+ // Simulate dragging an app with hide-drag-source set for the second (top most) app
+ val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
+ data.description.extras =
+ PersistableBundle().apply {
+ putInt(
+ EXTRA_HIDE_DRAG_SOURCE_TASK_ID,
+ runningTasks.last().taskId
+ )
+ }
+
+ // Start a new drag session
+ val session = DragSession(activityTaskManager, displayLayout, data, 0)
+ session.updateRunningTask()
+
+ assertThat(session.hideDragSourceTaskId).isEqualTo(runningTasks.last().taskId)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt
new file mode 100644
index 000000000000..1680d9b9a86c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragTestUtils.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.draganddrop
+
+import android.app.ActivityManager
+import android.app.PendingIntent
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.os.Process
+import java.util.Random
+import org.mockito.Mockito
+
+/**
+ * Convenience methods for drag tests.
+ */
+object DragTestUtils {
+ /**
+ * Creates an app-based clip data that is by default resizeable.
+ */
+ @JvmStatic
+ fun createAppClipData(mimeType: String): ClipData {
+ val clipDescription = ClipDescription(mimeType, arrayOf(mimeType))
+ val i = Intent()
+ when (mimeType) {
+ ClipDescription.MIMETYPE_APPLICATION_SHORTCUT -> {
+ i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package")
+ i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id")
+ }
+
+ ClipDescription.MIMETYPE_APPLICATION_TASK -> i.putExtra(Intent.EXTRA_TASK_ID, 12345)
+ ClipDescription.MIMETYPE_APPLICATION_ACTIVITY -> {
+ val pi = Mockito.mock(PendingIntent::class.java)
+ Mockito.doReturn(Process.myUserHandle()).`when`(pi).creatorUserHandle
+ i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi)
+ }
+ }
+ i.putExtra(Intent.EXTRA_USER, Process.myUserHandle())
+ val item = ClipData.Item(i)
+ item.activityInfo = ActivityInfo()
+ item.activityInfo.applicationInfo = ApplicationInfo()
+ val data = ClipData(clipDescription, item)
+ setClipDataResizeable(data, true)
+ return data
+ }
+
+ /**
+ * Creates an intent-based clip data that is by default resizeable.
+ */
+ @JvmStatic
+ fun createIntentClipData(intent: PendingIntent): ClipData {
+ val clipDescription = ClipDescription(
+ "Intent",
+ arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT)
+ )
+ val item = ClipData.Item.Builder()
+ .setIntentSender(intent.intentSender)
+ .build()
+ item.activityInfo = ActivityInfo()
+ item.activityInfo.applicationInfo = ApplicationInfo()
+ val data = ClipData(clipDescription, item)
+ setClipDataResizeable(data, true)
+ return data
+ }
+
+ /**
+ * Sets the given clip data to be resizeable.
+ */
+ @JvmStatic
+ fun setClipDataResizeable(data: ClipData, resizeable: Boolean) {
+ data.getItemAt(0).activityInfo.resizeMode = if (resizeable)
+ ActivityInfo.RESIZE_MODE_RESIZEABLE
+ else
+ ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+ }
+
+ /**
+ * Creates a task info with the given params.
+ */
+ @JvmStatic
+ fun createTaskInfo(winMode: Int, actType: Int): ActivityManager.RunningTaskInfo {
+ return createTaskInfo(winMode, actType, false)
+ }
+
+ /**
+ * Creates a task info with the given params.
+ */
+ @JvmStatic
+ fun createTaskInfo(winMode: Int, actType: Int, alwaysOnTop: Boolean = false):
+ ActivityManager.RunningTaskInfo {
+ val info = ActivityManager.RunningTaskInfo()
+ info.taskId = Random().nextInt()
+ info.configuration.windowConfiguration.activityType = actType
+ info.configuration.windowConfiguration.windowingMode = winMode
+ info.configuration.windowConfiguration.isAlwaysOnTop = alwaysOnTop
+ info.isVisible = true
+ info.isResizeable = true
+ info.baseActivity = ComponentName(
+ "com.android.wm.shell",
+ ".ActivityWithMode$winMode"
+ )
+ info.baseIntent = Intent()
+ info.baseIntent.setComponent(info.baseActivity)
+ val activityInfo = ActivityInfo()
+ activityInfo.packageName = info.baseActivity!!.packageName
+ activityInfo.name = info.baseActivity!!.className
+ info.topActivityInfo = activityInfo
+ return info
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
index eb74218b866a..2cfce6933e1b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
@@ -22,11 +22,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;
+import static com.android.wm.shell.draganddrop.DragTestUtils.createIntentClipData;
+import static com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo;
+import static com.android.wm.shell.draganddrop.DragTestUtils.setClipDataResizeable;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -55,11 +56,7 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
@@ -176,74 +173,11 @@ public class SplitDragPolicyTest extends ShellTestCase {
mMockitoSession.finishMocking();
}
- /**
- * Creates an app-based clip data that is by default resizeable.
- */
- private ClipData createAppClipData(String mimeType) {
- ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType });
- Intent i = new Intent();
- switch (mimeType) {
- case MIMETYPE_APPLICATION_SHORTCUT:
- i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package");
- i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id");
- break;
- case MIMETYPE_APPLICATION_TASK:
- i.putExtra(Intent.EXTRA_TASK_ID, 12345);
- break;
- case MIMETYPE_APPLICATION_ACTIVITY:
- final PendingIntent pi = mock(PendingIntent.class);
- doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle();
- i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi);
- break;
- }
- i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
- ClipData.Item item = new ClipData.Item(i);
- item.setActivityInfo(new ActivityInfo());
- ClipData data = new ClipData(clipDescription, item);
- setClipDataResizeable(data, true);
- return data;
- }
-
- /**
- * Creates an intent-based clip data that is by default resizeable.
- */
- private ClipData createIntentClipData(PendingIntent intent) {
- ClipDescription clipDescription = new ClipDescription("Intent",
- new String[] { MIMETYPE_TEXT_INTENT });
- ClipData.Item item = new ClipData.Item.Builder()
- .setIntentSender(intent.getIntentSender())
- .build();
- ClipData data = new ClipData(clipDescription, item);
- return data;
- }
-
- private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) {
- ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.configuration.windowConfiguration.setActivityType(actType);
- info.configuration.windowConfiguration.setWindowingMode(winMode);
- info.isResizeable = true;
- info.baseActivity = new ComponentName(getInstrumentation().getContext(),
- ".ActivityWithMode" + winMode);
- info.baseIntent = new Intent();
- info.baseIntent.setComponent(info.baseActivity);
- ActivityInfo activityInfo = new ActivityInfo();
- activityInfo.packageName = info.baseActivity.getPackageName();
- activityInfo.name = info.baseActivity.getClassName();
- info.topActivityInfo = activityInfo;
- return info;
- }
-
private void setRunningTask(ActivityManager.RunningTaskInfo task) {
doReturn(Collections.singletonList(task)).when(mActivityTaskManager)
.getTasks(anyInt(), anyBoolean());
}
- private void setClipDataResizeable(ClipData data, boolean resizeable) {
- data.getItemAt(0).getActivityInfo().resizeMode = resizeable
- ? ActivityInfo.RESIZE_MODE_RESIZEABLE
- : ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- }
-
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 72950a8dc139..6d37ed766aef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -28,8 +28,11 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
.setContainerLayer()
.setName("FakeLeash")
.build();
+ mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class);
}
@Test
@@ -93,7 +97,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -107,14 +112,16 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
oldAnimator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -145,7 +152,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
// Fullscreen to PiP.
PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
// Apply fraction 1 to compute the end value.
animator.applySurfaceControlTransaction(mLeash, tx, 1);
final Rect rotatedEndBounds = new Rect(endBounds);
@@ -157,7 +165,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
startBounds.set(0, 0, 1000, 500);
endBounds.set(200, 100, 400, 500);
animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
- endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
animator.applySurfaceControlTransaction(mLeash, tx, 1);
rotatedEndBounds.set(endBounds);
rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
@@ -166,6 +175,37 @@ public class PipAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() {
+ final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
+ final Rect startBounds = new Rect(200, 700, 400, 800);
+ final Rect endBounds = new Rect(0, 0, 500, 1000);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+
+ // Fullscreen task to PiP.
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
+ // Apply fraction 1 to compute the end value.
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+ assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+ animator.mCurrentValue);
+
+ // PiP to fullscreen.
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+ startBounds.set(0, 0, 1000, 500);
+ endBounds.set(200, 100, 400, 500);
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+ assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+ animator.mCurrentValue);
+ }
+
+ @Test
@SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
final Rect baseValue = new Rect(0, 0, 100, 100);
@@ -174,7 +214,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.updateEndValue(endValue2);
@@ -188,7 +229,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
@@ -207,4 +249,126 @@ public class PipAnimationControllerTest extends ShellTestCase {
verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
any(SurfaceControl.Transaction.class), eq(animator));
}
+
+ @Test
+ public void pipTransitionAnimator_overrideMainWindowFrame() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue());
+ assertEquals("Expect start value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is overridden for leave-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_letterboxed_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed();
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_sizeCompat_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat();
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index bcb7461bfae7..5f58265b45f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -47,6 +47,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.MockSurfaceControlHelper;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -61,6 +62,7 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -90,6 +92,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
+ @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
private TestShellExecutor mMainExecutor;
@@ -120,8 +124,10 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
- Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
- mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
+ Optional.empty() /* pipPerfHintControllerOptional */,
+ mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+ mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
+ mMainExecutor);
mMainExecutor.flushAll();
preparePipTaskOrg();
preparePipSurfaceTransactionHelper();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index e19a10a78417..b816f0ef041e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -93,6 +93,17 @@ public class PipExpandAnimatorTest {
.thenReturn(mMockTransaction);
when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
.thenReturn(mMockTransaction);
+ // No-op on the mMockStartTransaction
+ when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
// Do the same for mMockFinishTransaction
when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
.thenReturn(mMockFinishTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
new file mode 100644
index 000000000000..cab625216236
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipScheduler}
+ */
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipSchedulerTest {
+ private static final int TEST_RESIZE_DURATION = 1;
+ private static final Rect TEST_STARTING_BOUNDS = new Rect(0, 0, 10, 10);
+ private static final Rect TEST_BOUNDS = new Rect(0, 0, 20, 20);
+
+ @Mock private Context mMockContext;
+ @Mock private Resources mMockResources;
+ @Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private ShellExecutor mMockMainExecutor;
+ @Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipTransitionController mMockPipTransitionController;
+ @Mock private Runnable mMockUpdateMovementBoundsRunnable;
+ @Mock private WindowContainerToken mMockPipTaskToken;
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+ @Mock private PipAlphaAnimator mMockAlphaAnimator;
+
+ @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+ @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
+
+ private PipScheduler mPipScheduler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockPipBoundsState.getBounds()).thenReturn(TEST_STARTING_BOUNDS);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockTransaction);
+
+ mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
+ mMockPipTransitionState);
+ mPipScheduler.setPipTransitionController(mMockPipTransitionController);
+ mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+ mMockAlphaAnimator);
+
+ SurfaceControl testLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipSchedulerTest")
+ .setCallsite("PipSchedulerTest")
+ .build();
+ when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+ }
+
+ @Test
+ public void scheduleExitPipViaExpand_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleExitPipViaExpand();
+
+ verify(mMockMainExecutor, never()).execute(any());
+ }
+
+ @Test
+ public void scheduleExitPipViaExpand_exitTransitionCalled() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleExitPipViaExpand();
+
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockPipTransitionController, times(1))
+ .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ }
+
+ @Test
+ public void removePipAfterAnimation() {
+ //TODO: Update once this is changed to run animation as part of transition
+ setMockPipTaskToken();
+
+ mPipScheduler.removePipAfterAnimation();
+ verify(mMockAlphaAnimator, times(1))
+ .setAnimationEndCallback(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ verify(mMockAlphaAnimator, times(1)).start();
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockPipTransitionController, times(1))
+ .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_bounds_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfig_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
+
+ verify(mMockPipTransitionController, times(1))
+ .startResizeTransition(mWctArgumentCaptor.capture(), anyInt());
+ assertNotNull(mWctArgumentCaptor.getValue());
+ assertNotNull(mWctArgumentCaptor.getValue().getChanges());
+ boolean hasConfigAtEndChange = false;
+ for (WindowContainerTransaction.Change change :
+ mWctArgumentCaptor.getValue().getChanges().values()) {
+ if (change.getConfigAtTransitionEnd()) {
+ hasConfigAtEndChange = true;
+ break;
+ }
+ }
+ assertTrue(hasConfigAtEndChange);
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_boundsConfigDuration_nullTaskToken_noop() {
+ setNullPipTaskToken();
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_notInPip_noop() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(false);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, never()).startResizeTransition(any(), anyInt());
+ }
+
+ @Test
+ public void scheduleAnimateResizePip_resizeTransition() {
+ setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
+
+ mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
+
+ verify(mMockPipTransitionController, times(1))
+ .startResizeTransition(any(), eq(TEST_RESIZE_DURATION));
+ }
+
+ @Test
+ public void scheduleUserResizePip_emptyBounds_noop() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(new Rect());
+
+ verify(mMockTransaction, never()).apply();
+ }
+
+ @Test
+ public void scheduleUserResizePip_rotation_emptyBounds_noop() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(new Rect(), 90);
+
+ verify(mMockTransaction, never()).apply();
+ }
+
+ @Test
+ public void scheduleUserResizePip_applyTransaction() {
+ setMockPipTaskToken();
+
+ mPipScheduler.scheduleUserResizePip(TEST_BOUNDS, 90);
+
+ verify(mMockTransaction, times(1)).apply();
+ }
+
+ @Test
+ public void finishResize_movementBoundsRunnableCalled() {
+ mPipScheduler.setUpdateMovementBoundsRunnable(mMockUpdateMovementBoundsRunnable);
+ mPipScheduler.scheduleFinishResizePip(TEST_BOUNDS);
+
+ verify(mMockUpdateMovementBoundsRunnable, times(1)).run();
+ }
+
+ private void setNullPipTaskToken() {
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null);
+ }
+
+ private void setMockPipTaskToken() {
+ when(mMockPipTransitionState.getPipTaskToken()).thenReturn(mMockPipTaskToken);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
new file mode 100644
index 000000000000..89cb729d17b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.mockito.kotlin.VerificationKt.clearInvocations;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.app.PictureInPictureParams;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Rational;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test against {@link PipTaskListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipTaskListenerTest {
+
+ @Mock private Context mMockContext;
+ @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+ @Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private SurfaceControl mMockLeash;
+ @Mock private PipScheduler mMockPipScheduler;
+ @Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+ @Mock private ShellExecutor mMockShellExecutor;
+
+ @Mock private Icon mMockIcon;
+ @Mock private PendingIntent mMockPendingIntent;
+
+ @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback;
+
+ @Mock private PipResizeAnimator mMockPipResizeAnimator;
+
+ private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor;
+
+ private PipTaskListener mPipTaskListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class);
+ when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash);
+ }
+
+ @Test
+ public void constructor_addPipTransitionStateChangedListener() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+
+ verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener));
+ }
+
+ @Test
+ public void setPictureInPictureParams_updatePictureInPictureParams() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Rational aspectRatio = new Rational(4, 3);
+ String action1 = "action1";
+
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams();
+ assertEquals(aspectRatio, params.getAspectRatio());
+ assertTrue(params.hasSetActions());
+ assertEquals(1, params.getActions().size());
+ assertEquals(action1, params.getActions().get(0).getTitle());
+ }
+
+ @Test
+ public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ String action1 = "action1";
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ action1 = "modified action1";
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ verify(mMockPipParamsChangedCallback).onActionsChanged(
+ mRemoteActionListCaptor.capture(), any());
+ assertEquals(1, mRemoteActionListCaptor.getValue().size());
+ assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle());
+ }
+
+ @Test
+ public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ String action1 = "action1";
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+ aspectRatio, action1));
+
+ verifyZeroInteractions(mMockPipParamsChangedCallback);
+ }
+
+ @Test
+ public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+ String action1 = "action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ clearInvocations(mMockPipBoundsState);
+ action1 = "modified action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ verify(mMockPipTransitionState, times(0))
+ .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+ verify(mMockPipParamsChangedCallback).onActionsChanged(
+ mRemoteActionListCaptor.capture(), any());
+ assertEquals(1, mRemoteActionListCaptor.getValue().size());
+ assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle());
+ }
+
+ @Test
+ public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+ String action1 = "action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ clearInvocations(mMockPipBoundsState);
+ aspectRatio = new Rational(16, 9);
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+ verifyZeroInteractions(mMockPipParamsChangedCallback);
+ }
+
+ @Test
+ public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+ String action1 = "action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verify(mMockPipTransitionState, times(0))
+ .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+ }
+
+ @Test
+ public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Bundle extras = new Bundle();
+ extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras);
+
+ verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Bundle extras = new Bundle();
+ extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false);
+
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.UNDEFINED,
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+ extras);
+
+ verifyZeroInteractions(mMockPipScheduler);
+ }
+
+ @Test
+ public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Bundle extras = new Bundle();
+ extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+ extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS,
+ new Rect(0, 0, 100, 100));
+ when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200));
+
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.UNDEFINED,
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+ extras);
+ mPipTaskListener.setPipResizeAnimatorSupplier(
+ (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds,
+ duration, delta) -> mMockPipResizeAnimator);
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+ PipTransitionState.CHANGING_PIP_BOUNDS,
+ extras);
+
+ verify(mMockPipResizeAnimator, times(1)).start();
+ }
+
+ @Test
+ public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ Bundle extras = new Bundle();
+ extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false);
+ extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS,
+ new Rect(0, 0, 100, 100));
+ when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200));
+
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.UNDEFINED,
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+ extras);
+ mPipTaskListener.setPipResizeAnimatorSupplier(
+ (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds,
+ duration, delta) -> mMockPipResizeAnimator);
+ mPipTaskListener.onPipTransitionStateChanged(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+ PipTransitionState.CHANGING_PIP_BOUNDS,
+ extras);
+
+ verify(mMockPipResizeAnimator, times(0)).start();
+ }
+
+ private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio,
+ String... actions) {
+ final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+ builder.setAspectRatio(aspectRatio);
+ final List<RemoteAction> remoteActions = new ArrayList<>();
+ for (String action : actions) {
+ remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent));
+ }
+ if (!remoteActions.isEmpty()) {
+ builder.setActions(remoteActions);
+ }
+ return builder.build();
+ }
+
+ private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio,
+ String... actions) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions);
+ return taskInfo;
+ }
+}
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/GroupedTaskInfoTest.kt
index 0c100fca2036..2b30bc360d06 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/GroupedTaskInfoTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.recents
import android.app.ActivityManager
+import android.app.TaskInfo
import android.graphics.Rect
import android.os.Parcel
import android.testing.AndroidTestingRunner
@@ -24,11 +25,10 @@ import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.GroupedTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT
import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Correspondence
@@ -39,15 +39,15 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.mock
/**
- * Tests for [GroupedRecentTaskInfo]
+ * Tests for [GroupedTaskInfo]
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class GroupedRecentTaskInfoTest : ShellTestCase() {
+class GroupedTaskInfoTest : ShellTestCase() {
@Test
fun testSingleTask_hasCorrectType() {
- assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE)
+ assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN)
}
@Test
@@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
- assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
+ assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN)
assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
assertThat(recentTaskInfoParcel.taskInfo2).isNull()
}
@@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT)
assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
@@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3)
// Only compare task ids
- val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>(
+ val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
{ it?.taskId }, "has taskId of"
)
assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
@@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
}
@@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
token = WindowContainerToken(mock(IWindowContainerToken::class.java))
}
- private fun singleTaskGroupInfo(): GroupedRecentTaskInfo {
+ private fun singleTaskGroupInfo(): GroupedTaskInfo {
val task = createTaskInfo(id = 1)
- return GroupedRecentTaskInfo.forSingleTask(task)
+ return GroupedTaskInfo.forFullscreenTasks(task)
}
- private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
+ private fun splitTasksGroupInfo(): GroupedTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
- return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
+ return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
private fun freeformTasksGroupInfo(
freeformTaskIds: Array<Int>,
minimizedTaskIds: Array<Int> = emptyArray()
- ): GroupedRecentTaskInfo {
- return GroupedRecentTaskInfo.forFreeformTasks(
- freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(),
+ ): GroupedTaskInfo {
+ return GroupedTaskInfo.forFreeformTasks(
+ freeformTaskIds.map { createTaskInfo(it) }.toList(),
minimizedTaskIds.toSet())
}
}
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 9b73d53e0639..dede583ca970 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
@@ -46,6 +46,7 @@ import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitBounds;
@@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddRemoveSplitNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
@@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddSameSplitBoundsInfoSkipNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
// Verify only one update if the split info is the same
@@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertGroupedTasksListEquals(recentTasks,
t1.taskId, -1,
t2.taskId, -1,
@@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_withPairs() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
@@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertGroupedTasksListEquals(recentTasks,
t1.taskId, -1,
t2.taskId, t4.taskId,
@@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
@SuppressWarnings("unchecked")
- final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
- Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ final List<GroupedTaskInfo>[] recentTasks = new List[1];
+ Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
@@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
mRecentTasksController.asRecentTasks()
- .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+ .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run,
+ consumer);
mMainExecutor.flushAll();
assertGroupedTasksListEquals(recentTasks[0],
@@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
// Check freeform group entries
assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
@@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
SplitBounds pair1Bounds =
@@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo splitGroup = recentTasks.get(0);
+ GroupedTaskInfo freeformGroup = recentTasks.get(1);
+ GroupedTaskInfo singleGroup = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType());
// Check freeform group entries
assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
@@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase {
ExtendedMockito.doReturn(false)
.when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// Expect no grouping of tasks
assertEquals(4, recentTasks.size());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType());
assertEquals(t1, recentTasks.get(0).getTaskInfo1());
assertEquals(t2, recentTasks.get(1).getTaskInfo1());
@@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
@@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
// Check freeform group entries
assertEquals(3, freeformGroup.getTaskInfoList().size());
@@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
@@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertEquals(1, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
// Check bounds
assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
@@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testRemovedTaskRemovesSplit() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
// Add a pair
@@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testTaskWindowingModeChangedNotifiesChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t1 = makeTaskInfo(1);
setRawList(t1);
// Remove one of the tasks and ensure the pair is removed
@@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
- verify(mRecentTasksListener).onTaskMovedToFront(taskInfo);
+ GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+ verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask }));
}
@Test
@@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to create a task with a given task id.
*/
- private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
- ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+ private RecentTaskInfo makeTaskInfo(int taskId) {
+ RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
info.lastNonFullscreenBounds = new Rect();
return info;
@@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to set the raw task list on the controller.
*/
- private ArrayList<ActivityManager.RecentTaskInfo> setRawList(
- ActivityManager.RecentTaskInfo... tasks) {
- ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>();
- for (ActivityManager.RecentTaskInfo task : tasks) {
+ private ArrayList<RecentTaskInfo> setRawList(
+ RecentTaskInfo... tasks) {
+ ArrayList<RecentTaskInfo> rawList = new ArrayList<>();
+ for (RecentTaskInfo task : tasks) {
rawList.add(task);
}
doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
@@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
* @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
* the grouped task list
*/
- private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
+ private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
int... expectedTaskIds) {
int[] flattenedTaskIds = new int[recentTasks.size() * 2];
for (int i = 0; i < recentTasks.size(); i++) {
- GroupedRecentTaskInfo pair = recentTasks.get(i);
+ GroupedTaskInfo pair = recentTasks.get(i);
int taskId1 = pair.getTaskInfo1().taskId;
flattenedTaskIds[2 * i] = taskId1;
flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
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 ef3af8e7bdac..966651f19711 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
@@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
// Put the same component to the top running task
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
@@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
// Put the same component to the top running task
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
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 6cde0569796d..2442a55d78d0 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -363,6 +364,25 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void testTransitionFilterWindowingMode() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mWindowingMode = WINDOWING_MODE_FREEFORM;
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final TransitionInfo fullscreenStd = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, createTaskInfo(
+ 1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD)).build();
+ assertFalse(filter.matches(fullscreenStd));
+
+ final TransitionInfo freeformStd = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, createTaskInfo(
+ 1, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD)).build();
+ assertTrue(filter.matches(freeformStd));
+ }
+
+ @Test
public void testTransitionFilterMultiRequirement() {
// filter that requires at-least one opening and one closing app
TransitionFilter filter = new TransitionFilter();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 5ebf5170bf86..59141ca39487 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.content.ComponentName
+import android.graphics.Region
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.InsetsState
@@ -33,6 +34,9 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class CaptionWindowDecorationTests : ShellTestCase() {
+
+ private val exclusionRegion = Region.obtain()
+
@Test
fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
val taskInfo = createTaskInfo()
@@ -50,7 +54,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
@@ -72,7 +77,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
@@ -90,7 +96,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
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
index 56267174ba75..be664f86e9f5 100644
--- 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
@@ -30,6 +30,7 @@ import android.content.Intent
import android.content.Intent.ACTION_MAIN
import android.content.pm.ActivityInfo
import android.graphics.Rect
+import android.graphics.Region
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
@@ -48,6 +49,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
+import android.view.ISystemGestureExclusionListener
import android.view.IWindowManager
import android.view.InputChannel
import android.view.InputMonitor
@@ -84,7 +86,6 @@ 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.MultiInstanceHelper
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
@@ -131,6 +132,7 @@ import org.mockito.Mockito.times
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
@@ -175,7 +177,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockInputMonitorFactory:
DesktopModeWindowDecorViewModel.InputMonitorFactory
@Mock private lateinit var mockShellController: ShellController
- @Mock private lateinit var mockShellExecutor: ShellExecutor
+ private val testShellExecutor = TestShellExecutor()
@Mock private lateinit var mockAppHeaderViewHolderFactory: AppHeaderViewHolder.Factory
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@@ -230,13 +232,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
spyContext = spy(mContext)
doNothing().`when`(spyContext).startActivity(any())
- shellInit = ShellInit(mockShellExecutor)
+ shellInit = ShellInit(testShellExecutor)
windowDecorByTaskIdSpy.clear()
spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
desktopModeEventLogger = mock<DesktopModeEventLogger>()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
- mockShellExecutor,
+ testShellExecutor,
mockMainHandler,
mockMainChoreographer,
bgExecutor,
@@ -1307,10 +1309,101 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(decor).closeMaximizeMenu()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+ fun testOnTaskInfoChanged_enableShellTransitionsFlag() {
+ val task = createTask(
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ val taskSurface = SurfaceControl()
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task, taskSurface)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+
+ decoration.mHasGlobalFocus = true
+ desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+ verify(decoration).relayout(eq(task), eq(true), anyOrNull())
+
+ decoration.mHasGlobalFocus = false
+ desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+ verify(decoration).relayout(eq(task), eq(false), anyOrNull())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+ fun testOnTaskInfoChanged_disableShellTransitionsFlag() {
+ val task = createTask(
+ windowingMode = WINDOWING_MODE_FREEFORM
+ )
+ val taskSurface = SurfaceControl()
+ val decoration = setUpMockDecorationForTask(task)
+
+ onTaskOpening(task, taskSurface)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+
+ task.isFocused = true
+ desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+ verify(decoration).relayout(eq(task), eq(true), anyOrNull())
+
+ task.isFocused = false
+ desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+ verify(decoration).relayout(eq(task), eq(false), anyOrNull())
+ }
+
+ @Test
+ fun testGestureExclusionChanged_updatesDecorations() {
+ val captor = argumentCaptor<ISystemGestureExclusionListener>()
+ verify(mockWindowManager)
+ .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY))
+ val task = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val task2 = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val newRegion = Region.obtain().apply {
+ set(Rect(0, 0, 1600, 80))
+ }
+
+ captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion)
+ testShellExecutor.flushAll()
+
+ verify(task).onExclusionRegionChanged(newRegion)
+ verify(task2).onExclusionRegionChanged(newRegion)
+ }
+
+ @Test
+ fun testGestureExclusionChanged_otherDisplay_skipsDecorationUpdate() {
+ val captor = argumentCaptor<ISystemGestureExclusionListener>()
+ verify(mockWindowManager)
+ .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY))
+ val task = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val task2 = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = 2
+ )
+ val newRegion = Region.obtain().apply {
+ set(Rect(0, 0, 1600, 80))
+ }
+
+ captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion)
+ testShellExecutor.flushAll()
+
+ verify(task).onExclusionRegionChanged(newRegion)
+ verify(task2, never()).onExclusionRegionChanged(newRegion)
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
requestingImmersive: Boolean = false,
+ displayId: Int = DEFAULT_DISPLAY,
onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
onImmersiveOrRestoreListenerCaptor: KArgumentCaptor<() -> Unit> =
@@ -1334,6 +1427,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
): DesktopModeWindowDecoration {
val decor = setUpMockDecorationForTask(createTask(
windowingMode = windowingMode,
+ displayId = displayId,
requestingImmersive = requestingImmersive
))
onTaskOpening(decor.mTaskInfo, taskSurface)
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
index 41f57ae0fd97..1d2d0f078817 100644
--- 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
@@ -64,6 +64,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Region;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
@@ -224,6 +225,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private TestableContext mTestableContext;
private final ShellExecutor mBgExecutor = new TestShellExecutor();
private final AssistContent mAssistContent = new AssistContent();
+ private final Region mExclusionRegion = Region.obtain();
/** Set up run before test class. */
@BeforeClass
@@ -262,8 +264,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
- anyInt(), anyInt()))
+ anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(),
+ anyInt(), anyInt(), anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -283,7 +285,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor =
spy(createWindowDecoration(taskInfo));
- spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion);
// Menus should close if open before the task being invisible causes relayout to return.
verify(spyWindowDecor).closeHandleMenu();
@@ -303,7 +305,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
}
@@ -324,7 +327,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
}
@@ -350,7 +354,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity);
}
@@ -377,12 +382,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity);
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -400,12 +407,39 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isTrue();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_freeformAndTransparentAppearance_limitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isTrue();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -422,12 +456,38 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_freeformButOpaqueAppearance_unlimitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -443,12 +503,36 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_fullscreen_unlimitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse();
+ }
+
+ @Test
public void updateRelayoutParams_freeform_inputChannelNeeded() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -464,7 +548,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse();
}
@@ -486,7 +571,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -508,7 +594,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -531,7 +618,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
}
@@ -555,7 +643,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
}
@@ -577,7 +666,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
@@ -601,7 +691,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
@@ -631,7 +722,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
insetsState,
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -654,7 +746,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -676,7 +769,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -698,7 +792,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -719,7 +814,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -740,7 +836,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -762,7 +859,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -776,7 +874,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -798,7 +897,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -809,7 +909,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockTransaction).apply();
verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
@@ -824,7 +924,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockTransaction, never()).apply();
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
@@ -836,7 +936,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
}
@@ -848,7 +948,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -865,7 +965,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
verify(mMockHandler, never()).post(any());
@@ -877,11 +977,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
}
@@ -892,7 +992,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -1132,7 +1232,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
runnableArgument.getValue().run();
// Relayout decor with same captured link
- decor.relayout(taskInfo, true /* hasGlobalFocus */);
+ decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Verify handle menu's browser link not set to captured link since link is expired
createHandleMenu(decor);
@@ -1313,7 +1413,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any());
}
@@ -1330,7 +1430,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1357,7 +1457,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout(
runnableArgumentCaptor.capture());
runnableArgumentCaptor.getValue().invoke();
@@ -1380,7 +1480,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1400,7 +1500,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
createHandleMenu(spyWindowDecor);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
@@ -1425,7 +1525,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
createHandleMenu(spyWindowDecor);
spyWindowDecor.closeHandleMenu();
@@ -1440,9 +1540,30 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void browserApp_webUriUsedForBrowserApp() {
+ // Make {@link AppToWebUtils#isBrowserApp} return true
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.handleAllWebDataURI = true;
+ resolveInfo.activityInfo = createActivityInfo();
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(resolveInfo));
+
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+ TEST_URI3 /* generic link */);
+
+ // Verify web uri used for browser applications
+ createHandleMenu(decor);
+ verifyHandleMenuCreated(TEST_URI2);
+ }
+
+
private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
- any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
+ any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
anyInt(), anyInt(), anyInt(), anyInt());
}
@@ -1522,7 +1643,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
if (relayout) {
- windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
}
return windowDecor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index ade17c61eda1..7ec2cbf9460e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -242,7 +242,7 @@ class HandleMenuTest : ShellTestCase() {
private fun createAndShowHandleMenu(
splitPosition: Int? = null,
- forceShowSystemBars: Boolean = false,
+ forceShowSystemBars: Boolean = false
): HandleMenu {
val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
R.layout.desktop_mode_app_header
@@ -266,8 +266,9 @@ class HandleMenuTest : ShellTestCase() {
WindowManagerWrapper(mockWindowManager),
layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
- shouldShowChangeAspectRatioButton = false,
- null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ shouldShowChangeAspectRatioButton = false, isBrowserApp = false,
+ null /* openInAppOrBrowserIntent */, captionWidth = HANDLE_WIDTH,
+ captionHeight = 50,
captionX = captionX,
captionY = 0,
)
@@ -278,7 +279,7 @@ class HandleMenuTest : ShellTestCase() {
onNewWindowClickListener = mock(),
onManageWindowsClickListener = mock(),
onChangeAspectRatioClickListener = mock(),
- openInBrowserClickListener = mock(),
+ openInAppOrBrowserClickListener = mock(),
onOpenByDefaultClickListener = mock(),
onCloseMenuClickListener = mock(),
onOutsideTouchListener = mock(),
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 8e0434cb28f7..534803db5fe0 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
@@ -60,6 +60,7 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -508,7 +509,7 @@ public class WindowDecorationTests extends ShellTestCase {
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */);
+ true /* hasGlobalFocus */, Region.obtain());
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
}
@@ -525,7 +526,7 @@ public class WindowDecorationTests extends ShellTestCase {
mRelayoutParams.mCaptionTopPadding = 50;
windowDecor.relayout(taskInfo, false /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */);
+ true /* hasGlobalFocus */, Region.obtain());
assertEquals(50, mRelayoutResult.mCaptionTopPadding);
}
@@ -944,7 +945,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
- verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -958,7 +959,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
- verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -973,7 +974,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
assertTrue(decor.mIsKeyguardVisibleAndOccluded);
- verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -987,7 +988,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onKeyguardStateChanged(false /* visible */, true /* occluding */);
- verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any());
}
private ActivityManager.RunningTaskInfo createTaskInfo() {
@@ -1061,9 +1062,16 @@ public class WindowDecorationTests extends ShellTestCase {
surfaceControlViewHostFactory, desktopModeEventLogger);
}
- @Override
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
- relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus);
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus,
+ Region.obtain());
+ }
+
+ @Override
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus,
+ displayExclusionRegion);
}
@Override
@@ -1085,11 +1093,13 @@ public class WindowDecorationTests extends ShellTestCase {
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
- boolean applyStartTransactionOnDraw, boolean hasGlobalFocus) {
+ boolean applyStartTransactionOnDraw, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
mRelayoutParams.mLayoutResId = R.layout.caption_layout;
mRelayoutParams.mHasGlobalFocus = hasGlobalFocus;
+ mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 80ad1df44a1b..d8c1a11de452 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -24,6 +24,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
private val transitionsMock: Transitions = mock()
private val shellTaskOrganizerMock: ShellTaskOrganizer = mock()
private val desktopRepository: DesktopRepository = mock()
+ private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val toggleResizeDesktopTaskTransitionHandlerMock:
ToggleResizeDesktopTaskTransitionHandler =
mock()
@@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
toggleResizeDesktopTaskTransitionHandlerMock,
returnToDragStartAnimatorMock,
desktopRepository,
+ desktopModeEventLogger,
)
whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
}
@@ -127,7 +130,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
)
desktopTilingDecorViewModel.moveTaskToFrontIfTiled(task1)
- verify(desktopTilingDecoration, times(1)).moveTiledPairToFront(any())
+ verify(desktopTilingDecoration, times(1))
+ .moveTiledPairToFront(any(), isTaskFocused = eq(true))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index f371f5223419..d7b971de94ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -21,6 +21,7 @@ import android.content.res.Resources
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -33,6 +34,8 @@ 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.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private val info: TransitionInfo = mock()
private val finishCallback: Transitions.TransitionFinishCallback = mock()
private val desktopRepository: DesktopRepository = mock()
+ private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock()
+ private val motionEvent: MotionEvent = mock()
private lateinit var tilingDecoration: DesktopTilingWindowDecoration
private val split_divider_width = 10
@@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
desktopRepository,
+ desktopModeEventLogger,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
}
@@ -322,6 +328,37 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun taskTiled_broughtToFront_taskInfoNotUpdated_bringToFront() {
+ val task1 = createFreeformTask()
+ val task2 = createFreeformTask()
+ val task3 = createFreeformTask()
+ val stableBounds = STABLE_BOUNDS_MOCK
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+ whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock)
+ whenever(desktopRepository.isVisibleTask(any())).thenReturn(true)
+ tilingDecoration.onAppTiled(
+ task1,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.RIGHT,
+ BOUNDS,
+ )
+ tilingDecoration.onAppTiled(
+ task2,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.LEFT,
+ BOUNDS,
+ )
+
+ assertThat(tilingDecoration.moveTiledPairToFront(task3, isTaskFocused = true)).isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
+ }
+
+ @Test
fun taskTiledTasks_NotResized_BeforeTouchEndArrival() {
// Setup
val task1 = createFreeformTask()
@@ -371,13 +408,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
// End moving, no startTransition because bounds did not change.
tiledTaskHelper.newBounds.set(BOUNDS)
- tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
verify(tiledTaskHelper, times(2)).hideVeil()
verify(transitions, never()).startTransition(any(), any(), any())
// Move then end again with bounds changing to ensure startTransition is called.
tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
- tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
verify(transitions, times(1))
.startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration))
// No hide veil until start animation is called.
@@ -389,6 +426,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() {
+ // Setup
+ val task1 = createFreeformTask()
+ val task2 = createFreeformTask()
+ val stableBounds = STABLE_BOUNDS_MOCK
+ whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+ desktopWindowDecoration.mTaskInfo = task1
+ task1.minWidth = 0
+ task1.minHeight = 0
+ initTiledTaskHelperMock(task1)
+ desktopWindowDecoration.mDecorWindowContext = context
+ whenever(resources.getBoolean(any())).thenReturn(true)
+
+ // Act
+ tilingDecoration.onAppTiled(
+ task1,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.RIGHT,
+ BOUNDS,
+ )
+ tilingDecoration.onAppTiled(
+ task2,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.LEFT,
+ BOUNDS,
+ )
+ tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+ tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
+ tilingDecoration.onDividerHandleDragStart(motionEvent)
+ // Log start event for task1 and task2, but the tasks are the same in
+ // this test, so we verify the same log twice.
+ verify(desktopModeEventLogger, times(2)).logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ task1,
+ displayController,
+ )
+
+ tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
+ // Log end event for task1 and task2, but the tasks are the same in
+ // this test, so we verify the same log twice.
+ verify(desktopModeEventLogger, times(2)).logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ task1,
+ BOUNDS.height(),
+ BOUNDS.width(),
+ displayController,
+ )
+ }
+
+ @Test
fun taskTiled_shouldBeRemoved_whenTileBroken() {
val task1 = createFreeformTask()
val stableBounds = STABLE_BOUNDS_MOCK
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
index c96ce955f217..734815cdd915 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
@@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() {
val downMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true)
val motionEvent =
@@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() {
val upMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any())
}
@Test
@@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() {
val downMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
val upMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
- verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any())
+ verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any())
}
private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent {
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
index c6cee07d1946..5ab5a7a59c2a 100644
--- a/libs/appfunctions/Android.bp
+++ b/libs/appfunctions/Android.bp
@@ -18,10 +18,10 @@ package {
}
java_sdk_library {
- name: "com.google.android.appfunctions.sidecar",
+ name: "com.android.extensions.appfunctions",
owner: "google",
srcs: ["java/**/*.java"],
- api_packages: ["com.google.android.appfunctions.sidecar"],
+ api_packages: ["com.android.extensions.appfunctions"],
dex_preopt: {
enabled: false,
},
@@ -31,9 +31,9 @@ java_sdk_library {
}
prebuilt_etc {
- name: "appfunctions.sidecar.xml",
+ name: "appfunctions.extension.xml",
system_ext_specific: true,
sub_dir: "permissions",
- src: "appfunctions.sidecar.xml",
+ src: "appfunctions.extension.xml",
filename_from_src: true,
}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index faf84a8ab5ac..de402095e195 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -1,9 +1,29 @@
// Signature format: 2.0
-package com.google.android.appfunctions.sidecar {
+package com.android.extensions.appfunctions {
+
+ public final class AppFunctionException extends java.lang.Exception {
+ ctor public AppFunctionException(int, @Nullable String);
+ ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+ method public int getErrorCategory();
+ method public int getErrorCode();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+ field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+ field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+ field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+ field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+ field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_DENIED = 1000; // 0x3e8
+ field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+ field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+ field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+ }
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -15,7 +35,7 @@ package com.google.android.appfunctions.sidecar {
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -29,33 +49,17 @@ package com.google.android.appfunctions.sidecar {
public static final class ExecuteAppFunctionRequest.Builder {
ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build();
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
}
public final class ExecuteAppFunctionResponse {
- method public int getErrorCategory();
- method @Nullable public String getErrorMessage();
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method @NonNull public android.os.Bundle getExtras();
- method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- method public boolean isSuccess();
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
- field public static final int ERROR_CATEGORY_APP = 3; // 0x3
- field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
- field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
- field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
- field public static final String PROPERTY_RETURN_VALUE = "returnValue";
- field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
- field public static final int RESULT_CANCELLED = 2001; // 0x7d1
- field public static final int RESULT_DENIED = 1000; // 0x3e8
- field public static final int RESULT_DISABLED = 1002; // 0x3ea
- field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
- field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
- field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
+ field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
}
}
diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml
index bef8b6ec7ce6..dd09cc39d12f 100644
--- a/libs/appfunctions/appfunctions.sidecar.xml
+++ b/libs/appfunctions/appfunctions.extension.xml
@@ -16,6 +16,6 @@
-->
<permissions>
<library
- name="com.google.android.appfunctions.sidecar"
- file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/>
+ name="com.android.extensions.appfunctions"
+ file="/system_ext/framework/com.android.extensions.appfunctions.jar"/>
</permissions> \ No newline at end of file
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
new file mode 100644
index 000000000000..2540236f2ce5
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.extensions.appfunctions;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Represents an app function related errors. */
+public final class AppFunctionException extends Exception {
+ /**
+ * The caller does not have the permission to execute an app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DENIED = 1000;
+
+ /**
+ * The caller supplied invalid arguments to the execution request.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+ /**
+ * The caller tried to execute a disabled app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DISABLED = 1002;
+
+ /**
+ * The caller tried to execute a function that does not exist.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+ /**
+ * An internal unexpected error coming from the system.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_SYSTEM_ERROR = 2000;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_CANCELLED = 2001;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+ */
+ public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+ /**
+ * The error category is unknown.
+ *
+ * <p>This is the default value for {@link #getErrorCategory}.
+ */
+ public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * The error is caused by the app requesting a function execution.
+ *
+ * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+ * invalid function ID.
+ *
+ * <p>Errors in the category fall in the range 1000-1999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+ /**
+ * The error is caused by an issue in the system.
+ *
+ * <p>For example, the AppFunctionService implementation is not found by the system.
+ *
+ * <p>Errors in the category fall in the range 2000-2999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+ /**
+ * The error is caused by the app providing the function.
+ *
+ * <p>For example, the app crashed when the system is executing the request.
+ *
+ * <p>Errors in the category fall in the range 3000-3999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_APP = 3;
+
+ private final int mErrorCode;
+ @Nullable private final String mErrorMessage;
+ @NonNull private final Bundle mExtras;
+
+ public AppFunctionException(int errorCode, @Nullable String errorMessage) {
+ this(errorCode, errorMessage, Bundle.EMPTY);
+ }
+
+ public AppFunctionException(
+ int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ super(errorMessage);
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mExtras = extras;
+ }
+
+ /** Returns one of the {@code ERROR} constants. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Returns the error category.
+ *
+ * <p>This method categorizes errors based on their underlying cause, allowing developers to
+ * implement targeted error handling and provide more informative error messages to users. It
+ * maps ranges of error codes to specific error categories.
+ *
+ * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+ * any error category.
+ *
+ * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+ * error code ranges.
+ */
+ @ErrorCategory
+ public int getErrorCategory() {
+ if (mErrorCode >= 1000 && mErrorCode < 2000) {
+ return ERROR_CATEGORY_REQUEST_ERROR;
+ }
+ if (mErrorCode >= 2000 && mErrorCode < 3000) {
+ return ERROR_CATEGORY_SYSTEM;
+ }
+ if (mErrorCode >= 3000 && mErrorCode < 4000) {
+ return ERROR_CATEGORY_APP;
+ }
+ return ERROR_CATEGORY_UNKNOWN;
+ }
+
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Error codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_DENIED,
+ ERROR_APP_UNKNOWN_ERROR,
+ ERROR_FUNCTION_NOT_FOUND,
+ ERROR_SYSTEM_ERROR,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_DISABLED,
+ ERROR_CANCELLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /**
+ * Error categories.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_CATEGORY_"},
+ value = {
+ ERROR_CATEGORY_UNKNOWN,
+ ERROR_CATEGORY_REQUEST_ERROR,
+ ERROR_CATEGORY_APP,
+ ERROR_CATEGORY_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCategory {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 2075104ff868..9eb66a33fedc 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -31,7 +31,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Provides app functions related functionalities.
@@ -115,7 +114,9 @@ public final class AppFunctionManager {
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback) {
Objects.requireNonNull(sidecarRequest);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -126,10 +127,20 @@ public final class AppFunctionManager {
platformRequest,
executor,
cancellationSignal,
- (platformResponse) -> {
- callback.accept(
- SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(
+ android.app.appfunctions.ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ SidecarConverter.getSidecarExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(
+ android.app.appfunctions.AppFunctionException exception) {
+ callback.onError(
+ SidecarConverter.getSidecarAppFunctionException(exception));
+ }
});
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 0dc87e45b7e3..55f579138218 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
-import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -26,9 +27,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
-import android.util.Log;
-
-import java.util.function.Consumer;
+import android.os.OutcomeReceiver;
/**
* Abstract base class to provide app functions to the system.
@@ -80,10 +79,18 @@ public abstract class AppFunctionService extends Service {
platformRequest),
callingPackage,
cancellationSignal,
- (sidecarResponse) -> {
- callback.accept(
- SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ getPlatformExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ callback.onError(
+ getPlatformAppFunctionException(exception));
+ }
});
});
@@ -116,12 +123,14 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
+ * @param callback A callback to report back the result or error.
*/
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
index 593c5213dd52..baddc245f0f1 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
@@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest {
* Returns the function parameters. The key is the parameter name, and the value is the
* parameter value.
*
- * <p>The bundle may have missing parameters. Developers are advised to implement defensive
- * handling measures.
+ * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+ * implement defensive handling measures.
*
* <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
* obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 000000000000..0826f04a50dd
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.extensions.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/** The response to an app function execution. */
+public final class ExecuteAppFunctionResponse {
+ /**
+ * The name of the property that stores the function return value within the {@code
+ * resultDocument}.
+ *
+ * <p>See {@link GenericDocument#getProperty(String)} for more information.
+ *
+ * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
+ * be empty {@link GenericDocument}.
+ *
+ * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+ * return {@code null}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
+
+ /**
+ * Returns the return value of the executed function.
+ *
+ * <p>The return value is stored in a {@link GenericDocument} with the key {@link
+ * #PROPERTY_RETURN_VALUE}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ @NonNull private final GenericDocument mResultDocument;
+
+ /** Returns the additional metadata data relevant to this function execution response. */
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * @param resultDocument The return value of the executed function.
+ */
+ public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+ this(resultDocument, Bundle.EMPTY);
+ }
+
+ /**
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata for this function execution response.
+ */
+ public ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
+ }
+
+ /**
+ * Returns a generic document containing the return value of the executed function.
+ *
+ * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
+ *
+ * <p>Sample code for extracting the return value:
+ *
+ * <pre>
+ * GenericDocument resultDocument = response.getResultDocument();
+ * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+ * if (returnValue != null) {
+ * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+ * // {@link GenericDocument#getPropertyLong} etc.
+ * // Do something with the returnValue
+ * }
+ * </pre>
+ *
+ * @see AppFunctionManager on how to determine the expected function return.
+ */
+ @NonNull
+ public GenericDocument getResultDocument() {
+ return mResultDocument;
+ }
+
+ /** Returns the additional metadata for this function execution response. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
index b1b05f79f33f..5e1fc7e684e2 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
@@ -28,46 +28,50 @@ public final class SidecarConverter {
private SidecarConverter() {}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionRequest}
*
* @hide
*/
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionRequest
getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
- return new
- android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionResponse}
*
* @hide
*/
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionResponse
getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
- }
+ return new android.app.appfunctions.ExecuteAppFunctionResponse(
+ response.getResultDocument(), response.getExtras());
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * Converts sidecar's {@link AppFunctionException} into platform's {@link
+ * android.app.appfunctions.AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.AppFunctionException
+ getPlatformAppFunctionException(@NonNull AppFunctionException exception) {
+ return new android.app.appfunctions.AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's
+ * {@link ExecuteAppFunctionRequest}
*
* @hide
*/
@@ -75,30 +79,34 @@ public final class SidecarConverter {
public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
@NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
return new ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into
+ * sidecar's {@link ExecuteAppFunctionResponse}
*
* @hide
*/
@NonNull
public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
@NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
- }
+ return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras());
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.AppFunctionException} into
+ * sidecar's {@link AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static AppFunctionException getSidecarAppFunctionException(
+ @NonNull android.app.appfunctions.AppFunctionException exception) {
+ return new AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
deleted file mode 100644
index 4e88fb025a9d..000000000000
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.google.android.appfunctions.sidecar;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.appsearch.GenericDocument;
-import android.os.Bundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * The response to an app function execution.
- *
- * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
- * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
- * platform API).
- */
-public final class ExecuteAppFunctionResponse {
- /**
- * The name of the property that stores the function return value within the {@code
- * resultDocument}.
- *
- * <p>See {@link GenericDocument#getProperty(String)} for more information.
- *
- * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
- * be empty {@link GenericDocument}.
- *
- * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
- * return {@code null}.
- *
- * <p>See {@link #getResultDocument} for more information on extracting the return value.
- */
- public static final String PROPERTY_RETURN_VALUE = "returnValue";
-
- /**
- * The call was successful.
- *
- * <p>This result code does not belong in an error category.
- */
- public static final int RESULT_OK = 0;
-
- /**
- * The caller does not have the permission to execute an app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DENIED = 1000;
-
- /**
- * The caller supplied invalid arguments to the execution request.
- *
- * <p>This error may be considered similar to {@link IllegalArgumentException}.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_INVALID_ARGUMENT = 1001;
-
- /**
- * The caller tried to execute a disabled app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DISABLED = 1002;
-
- /**
- * The caller tried to execute a function that does not exist.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
- /**
- * An internal unexpected error coming from the system.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_SYSTEM_ERROR = 2000;
-
- /**
- * The operation was cancelled. Use this error code to report that a cancellation is done after
- * receiving a cancellation signal.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_CANCELLED = 2001;
-
- /**
- * An unknown error occurred while processing the call in the AppFunctionService.
- *
- * <p>This error is thrown when the service is connected in the remote application but an
- * unexpected error is thrown from the bound application.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
- */
- public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
- /**
- * The error category is unknown.
- *
- * <p>This is the default value for {@link #getErrorCategory}.
- */
- public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
- /**
- * The error is caused by the app requesting a function execution.
- *
- * <p>For example, the caller provided invalid parameters in the execution request e.g. an
- * invalid function ID.
- *
- * <p>Errors in the category fall in the range 1000-1999 inclusive.
- */
- public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
- /**
- * The error is caused by an issue in the system.
- *
- * <p>For example, the AppFunctionService implementation is not found by the system.
- *
- * <p>Errors in the category fall in the range 2000-2999 inclusive.
- */
- public static final int ERROR_CATEGORY_SYSTEM = 2;
-
- /**
- * The error is caused by the app providing the function.
- *
- * <p>For example, the app crashed when the system is executing the request.
- *
- * <p>Errors in the category fall in the range 3000-3999 inclusive.
- */
- public static final int ERROR_CATEGORY_APP = 3;
-
- /** The result code of the app function execution. */
- @ResultCode private final int mResultCode;
-
- /**
- * The error message associated with the result, if any. This is {@code null} if the result code
- * is {@link #RESULT_OK}.
- */
- @Nullable private final String mErrorMessage;
-
- /**
- * Returns the return value of the executed function.
- *
- * <p>The return value is stored in a {@link GenericDocument} with the key {@link
- * #PROPERTY_RETURN_VALUE}.
- *
- * <p>See {@link #getResultDocument} for more information on extracting the return value.
- */
- @NonNull private final GenericDocument mResultDocument;
-
- /** Returns the additional metadata data relevant to this function execution response. */
- @NonNull private final Bundle mExtras;
-
- private ExecuteAppFunctionResponse(
- @NonNull GenericDocument resultDocument,
- @NonNull Bundle extras,
- @ResultCode int resultCode,
- @Nullable String errorMessage) {
- mResultDocument = Objects.requireNonNull(resultDocument);
- mExtras = Objects.requireNonNull(extras);
- mResultCode = resultCode;
- mErrorMessage = errorMessage;
- }
-
- /**
- * Returns result codes from throwable.
- *
- * @hide
- */
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
- * Returns a successful response.
- *
- * @param resultDocument The return value of the executed function.
- * @param extras The additional metadata data relevant to this function execution response.
- */
- @NonNull
- public static ExecuteAppFunctionResponse newSuccess(
- @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
- Objects.requireNonNull(resultDocument);
- Bundle actualExtras = getActualExtras(extras);
-
- return new ExecuteAppFunctionResponse(
- resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
- }
-
- /**
- * Returns a failure response.
- *
- * @param resultCode The result code of the app function execution.
- * @param extras The additional metadata data relevant to this function execution response.
- * @param errorMessage The error message associated with the result, if any.
- */
- @NonNull
- public static ExecuteAppFunctionResponse newFailure(
- @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
- if (resultCode == RESULT_OK) {
- throw new IllegalArgumentException("resultCode must not be RESULT_OK");
- }
- Bundle actualExtras = getActualExtras(extras);
- GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
- return new ExecuteAppFunctionResponse(
- emptyDocument, actualExtras, resultCode, errorMessage);
- }
-
- private static Bundle getActualExtras(@Nullable Bundle extras) {
- if (extras == null) {
- return Bundle.EMPTY;
- }
- return extras;
- }
-
- /**
- * Returns the error category of the {@link ExecuteAppFunctionResponse}.
- *
- * <p>This method categorizes errors based on their underlying cause, allowing developers to
- * implement targeted error handling and provide more informative error messages to users. It
- * maps ranges of result codes to specific error categories.
- *
- * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
- * ensure correct categorization of the failed response.
- *
- * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
- * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
- *
- * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
- * result code ranges.
- */
- @ErrorCategory
- public int getErrorCategory() {
- if (mResultCode >= 1000 && mResultCode < 2000) {
- return ERROR_CATEGORY_REQUEST_ERROR;
- }
- if (mResultCode >= 2000 && mResultCode < 3000) {
- return ERROR_CATEGORY_SYSTEM;
- }
- if (mResultCode >= 3000 && mResultCode < 4000) {
- return ERROR_CATEGORY_APP;
- }
- return ERROR_CATEGORY_UNKNOWN;
- }
-
- /**
- * Returns a generic document containing the return value of the executed function.
- *
- * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
- *
- * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
- * function does not produce a return value.
- *
- * <p>Sample code for extracting the return value:
- *
- * <pre>
- * GenericDocument resultDocument = response.getResultDocument();
- * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
- * if (returnValue != null) {
- * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
- * // {@link GenericDocument#getPropertyLong} etc.
- * // Do something with the returnValue
- * }
- * </pre>
- */
- @NonNull
- public GenericDocument getResultDocument() {
- return mResultDocument;
- }
-
- /** Returns the extras of the app function execution. */
- @NonNull
- public Bundle getExtras() {
- return mExtras;
- }
-
- /**
- * Returns {@code true} if {@link #getResultCode} equals {@link
- * ExecuteAppFunctionResponse#RESULT_OK}.
- */
- public boolean isSuccess() {
- return getResultCode() == RESULT_OK;
- }
-
- /**
- * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
- */
- @ResultCode
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the error message associated with this result.
- *
- * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
- */
- @Nullable
- public String getErrorMessage() {
- return mErrorMessage;
- }
-
- /**
- * Result codes.
- *
- * @hide
- */
- @IntDef(
- prefix = {"RESULT_"},
- value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_SYSTEM_ERROR,
- RESULT_FUNCTION_NOT_FOUND,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED,
- RESULT_CANCELLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
-
- /**
- * Error categories.
- *
- * @hide
- */
- @IntDef(
- prefix = {"ERROR_CATEGORY_"},
- value = {
- ERROR_CATEGORY_UNKNOWN,
- ERROR_CATEGORY_REQUEST_ERROR,
- ERROR_CATEGORY_APP,
- ERROR_CATEGORY_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCategory {}
-}
diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp
index 6f5eff305d8d..db79675ae9f7 100644
--- a/libs/appfunctions/tests/Android.bp
+++ b/libs/appfunctions/tests/Android.bp
@@ -25,7 +25,7 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.core_core-ktx",
- "com.google.android.appfunctions.sidecar.impl",
+ "com.android.extensions.appfunctions.impl",
"junit",
"kotlin-test",
"mockito-target-extended-minus-junit4",
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
index 264f84209caf..11202d58e484 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar.tests
+package com.android.extensions.appfunctions.tests
+import android.app.appfunctions.AppFunctionException
import android.app.appfunctions.ExecuteAppFunctionRequest
import android.app.appfunctions.ExecuteAppFunctionResponse
import android.app.appsearch.GenericDocument
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.appfunctions.sidecar.SidecarConverter
+import com.android.extensions.appfunctions.SidecarConverter
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,7 +61,7 @@ class SidecarConverterTest {
.setPropertyLong("testLong", 23)
.build()
val sidecarRequest =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder(
+ com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder(
"targetPkg",
"targetFunctionId"
)
@@ -83,44 +84,38 @@ class SidecarConverterTest {
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null)
+ val platformResponse = ExecuteAppFunctionResponse(resultGd)
val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
platformResponse
)
- assertThat(sidecarResponse.isSuccess).isTrue()
assertThat(
sidecarResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(sidecarResponse.errorMessage).isNull()
}
@Test
- fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val platformResponse =
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getSidecarAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val platformException =
+ AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse
+ val sidecarException = SidecarConverter.getSidecarAppFunctionException(
+ platformException
)
- assertThat(sidecarResponse.isSuccess).isFalse()
- assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(sidecarResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(sidecarResponse.errorMessage).isNull()
+ assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(sidecarException.errorMessage).isEqualTo("error")
+ assertThat(sidecarException.extras.getString("key")).isEqualTo("value")
}
@Test
@@ -129,44 +124,39 @@ class SidecarConverterTest {
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse
- .newSuccess(resultGd, null)
+ val sidecarResponse =
+ com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd)
val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
sidecarResponse
)
- assertThat(platformResponse.isSuccess).isTrue()
assertThat(
platformResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(platformResponse.errorMessage).isNull()
}
@Test
- fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val sidecarResponse =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getPlatformAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val sidecarException =
+ com.android.extensions.appfunctions.AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse
+ val platformException = SidecarConverter.getPlatformAppFunctionException(
+ sidecarException
)
- assertThat(platformResponse.isSuccess).isFalse()
- assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(platformResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(platformResponse.errorMessage).isNull()
+ assertThat(platformException.errorCode)
+ .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(platformException.errorMessage).isEqualTo("error")
+ assertThat(platformException.extras.getString("key")).isEqualTo("value")
}
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b71abdc011c1..fcb7efc35c94 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -580,6 +580,7 @@ cc_defaults {
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
"utils/StringUtils.cpp",
+ "utils/StatsUtils.cpp",
"utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index fddcf29b9197..5f84f47b725d 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,9 +33,9 @@ inline bool letter_spacing_justification() {
#endif // __ANDROID__
}
-inline bool typeface_redesign() {
+inline bool typeface_redesign_readonly() {
#ifdef __ANDROID__
- static bool flag = com_android_text_flags_typeface_redesign();
+ static bool flag = com_android_text_flags_typeface_redesign_readonly();
return flag;
#else
return true;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index ae46a99f09c8..064cac2a6fc6 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -113,7 +113,6 @@ float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
bool Properties::clipSurfaceViews = false;
bool Properties::hdr10bitPlus = false;
bool Properties::skipTelemetry = false;
-bool Properties::resampleGainmapRegions = false;
bool Properties::queryGlobalPriority = false;
int Properties::timeoutMultiplier = 1;
@@ -190,8 +189,6 @@ bool Properties::load() {
clipSurfaceViews =
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
hdr10bitPlus = hwui_flags::hdr_10bit_plus();
- resampleGainmapRegions = base::GetBoolProperty("debug.hwui.resample_gainmap_regions",
- hwui_flags::resample_gainmap_regions());
queryGlobalPriority = hwui_flags::query_global_priority();
timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
@@ -288,5 +285,11 @@ bool Properties::initializeGlAlways() {
return base::GetBoolProperty(PROPERTY_INITIALIZE_GL_ALWAYS, hwui_flags::initialize_gl_always());
}
+bool Properties::resampleGainmapRegions() {
+ static bool sResampleGainmapRegions = base::GetBoolProperty(
+ "debug.hwui.resample_gainmap_regions", hwui_flags::resample_gainmap_regions());
+ return sResampleGainmapRegions;
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 6f84796fb11e..db930f3904de 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -345,7 +345,6 @@ public:
static bool clipSurfaceViews;
static bool hdr10bitPlus;
static bool skipTelemetry;
- static bool resampleGainmapRegions;
static bool queryGlobalPriority;
static int timeoutMultiplier;
@@ -381,6 +380,7 @@ public:
static void setDrawingEnabled(bool enable);
static bool initializeGlAlways();
+ static bool resampleGainmapRegions();
private:
static StretchEffectBehavior stretchEffectBehavior;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 5ad788c67816..fa27af671be6 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -154,3 +154,13 @@ flag {
description: "API's that enable animated image drawables to use nearest sampling when scaling."
bug: "370523334"
}
+
+flag {
+ name: "remove_vri_sketchy_destroy"
+ namespace: "core_graphics"
+ description: "Remove the eager yet thread-violating destroyHardwareResources in VRI#die"
+ bug: "377057106"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index e074a27db38f..a9a5db8181ba 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -27,8 +27,8 @@
#include <SkColorSpace.h>
#include <SkColorType.h>
#include <SkEncodedOrigin.h>
-#include <SkImageInfo.h>
#include <SkGainmapInfo.h>
+#include <SkImageInfo.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkPngChunkReader.h>
@@ -43,6 +43,8 @@
#include <memory>
+#include "modules/skcms/src/skcms_public.h"
+
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 9cd6e253140e..e5fb75575ac3 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -49,6 +49,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+ minikinPaint.verticalText = paint->isVerticalText();
const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
if (familyVariant.has_value()) {
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 1510ce1378d8..20acf981d9b9 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -73,7 +73,7 @@ public:
static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getSkFont().getSkewX();
bool savefakeBold = paint->getSkFont().isEmbolden();
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
uint32_t start = layout.getFontRunStart(runIdx);
uint32_t end = layout.getFontRunEnd(runIdx);
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 7eb849fe6e3d..594ea31387ad 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -158,6 +158,7 @@ public:
SkSamplingOptions sampling() const {
return SkSamplingOptions(this->filterMode());
}
+ bool isVerticalText() const { return mVerticalText; }
void setVariationOverride(minikin::VariationSettings&& varSettings) {
mFontVariationOverride = std::move(varSettings);
@@ -202,6 +203,7 @@ private:
bool mUnderline = false;
bool mDevKern = false;
minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
+ bool mVerticalText = false;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 6dfcedc3d918..fa5325d90218 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -49,7 +49,8 @@ Paint::Paint(const Paint& paint)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
, mDevKern(paint.mDevKern)
- , mRunFlag(paint.mRunFlag) {}
+ , mRunFlag(paint.mRunFlag)
+ , mVerticalText(paint.mVerticalText) {}
Paint::~Paint() {}
@@ -71,6 +72,7 @@ Paint& Paint::operator=(const Paint& other) {
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
mRunFlag = other.mRunFlag;
+ mVerticalText = other.mVerticalText;
return *this;
}
@@ -83,7 +85,8 @@ bool operator==(const Paint& a, const Paint& b) {
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
- a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
+ a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag &&
+ a.mVerticalText == b.mVerticalText;
}
void Paint::reset() {
@@ -97,6 +100,7 @@ void Paint::reset() {
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mVerticalText = false;
mRunFlag = minikin::RunFlag::NONE;
}
@@ -135,6 +139,7 @@ static const uint32_t sForceAutoHinting = 0x800;
// flags related to minikin::Paint
static const uint32_t sUnderlineFlag = 0x08;
static const uint32_t sStrikeThruFlag = 0x10;
+static const uint32_t sVerticalTextFlag = 0x1000;
static const uint32_t sTextRunLeftEdge = 0x2000;
static const uint32_t sTextRunRightEdge = 0x4000;
// flags no longer supported on native side (but mirrored for compatibility)
@@ -190,6 +195,7 @@ uint32_t Paint::getJavaFlags() const {
flags |= -(int)mUnderline & sUnderlineFlag;
flags |= -(int)mDevKern & sDevKernFlag;
flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+ flags |= -(int)mVerticalText & sVerticalTextFlag;
if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
flags |= sTextRunLeftEdge;
}
@@ -206,6 +212,7 @@ void Paint::setJavaFlags(uint32_t flags) {
mUnderline = (flags & sUnderlineFlag) != 0;
mDevKern = (flags & sDevKernFlag) != 0;
mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+ mVerticalText = (flags & sVerticalTextFlag) != 0;
std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
if (flags & sTextRunLeftEdge) {
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 49a7f73fb3a3..8b43f1db84af 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -10,6 +10,7 @@
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
+#include <utils/StatsUtils.h>
#include <memory>
@@ -630,6 +631,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
}
bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
outputBitmap.notifyPixelsChanged();
+ uirenderer::logBitmapDecode(*reuseBitmap);
// If a java bitmap was passed in for reuse, pass it back
return javaBitmap;
}
@@ -650,6 +652,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
}
}
+ uirenderer::logBitmapDecode(*hardwareBitmap);
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets, -1);
}
@@ -659,6 +662,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
heapBitmap->setGainmap(std::move(gainmap));
}
+ uirenderer::logBitmapDecode(*heapBitmap);
// now create the java bitmap
return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
-1);
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index f7e8e073a272..491066b05952 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -19,6 +19,7 @@
#include <HardwareBitmapUploader.h>
#include <androidfw/Asset.h>
#include <sys/stat.h>
+#include <utils/StatsUtils.h>
#include <memory>
@@ -111,9 +112,7 @@ public:
return false;
}
- // Round out the subset so that we decode a slightly larger region, in
- // case the subset has fractional components.
- SkIRect roundedSubset = desiredSubset.roundOut();
+ sampleSize = std::max(sampleSize, 1);
// Map the desired subset to the space of the decoded gainmap. The
// subset is repositioned relative to the resulting bitmap, and then
@@ -122,10 +121,51 @@ public:
// for existing gainmap formats.
SkRect logicalSubset = desiredSubset.makeOffset(-std::floorf(desiredSubset.left()),
-std::floorf(desiredSubset.top()));
- logicalSubset.fLeft /= sampleSize;
- logicalSubset.fTop /= sampleSize;
- logicalSubset.fRight /= sampleSize;
- logicalSubset.fBottom /= sampleSize;
+ logicalSubset = scale(logicalSubset, 1.0f / sampleSize);
+
+ // Round out the subset so that we decode a slightly larger region, in
+ // case the subset has fractional components. When we round, we need to
+ // round the downsampled subset to avoid possibly rounding down by accident.
+ // Consider this concrete example if we round the desired subset directly:
+ //
+ // * We are decoding a 18x18 corner of an image
+ //
+ // * Gainmap is 1/4 resolution, which is logically a 4.5x4.5 gainmap
+ // that we would want
+ //
+ // * The app wants to downsample by a factor of 2x
+ //
+ // * The desired gainmap dimensions are computed to be 3x3 to fit the
+ // downsampled gainmap, since we need to fill a 2.25x2.25 region that's
+ // later upscaled to 3x3
+ //
+ // * But, if we round out the desired gainmap region _first_, then we
+ // request to decode a 5x5 region, downsampled by 2, which actually
+ // decodes a 2x2 region since skia rounds down internally. But then we transfer
+ // the result to a 3x3 bitmap using a clipping allocator, which leaves an inset.
+ // Not only did we get a smaller region than we expected (so, our desired subset is
+ // not valid), but because the API allows for decoding regions using a recycled
+ // bitmap, we can't really safely fill in the inset since then we might
+ // extend the gainmap beyond intended the image bounds. Oops.
+ //
+ // * If we instead round out as if we downsampled, then we downsample
+ // the desired region to 2.25x2.25, round out to 3x3, then upsample back
+ // into the source gainmap space to get 6x6. Then we decode a 6x6 region
+ // downsampled into a 3x3 region, and everything's now correct.
+ //
+ // Note that we don't always run into this problem, because
+ // decoders actually round *up* for subsampling when decoding a subset
+ // that matches the dimensions of the image. E.g., if the original image
+ // size in the above example was a 20x20 image, so that the gainmap was
+ // 5x5, then we still manage to downsample into a 3x3 bitmap even with
+ // the "wrong" math. but that's what we wanted!
+ //
+ // Note also that if we overshoot the gainmap bounds with the requested
+ // subset it isn't a problem either, since now the decoded bitmap is too
+ // large, rather than too small, so now we can use the desired subset to
+ // avoid sampling "invalid" colors.
+ SkRect scaledSubset = scale(desiredSubset, 1.0f / sampleSize);
+ SkIRect roundedSubset = scale(scaledSubset.roundOut(), static_cast<float>(sampleSize));
RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false, logicalSubset);
if (!mGainmapBRD->decodeRegion(&bm, &allocator, roundedSubset, sampleSize, decodeColorType,
@@ -153,7 +193,7 @@ public:
const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
- if (uirenderer::Properties::resampleGainmapRegions) {
+ if (uirenderer::Properties::resampleGainmapRegions()) {
const auto srcRect = SkRect::MakeLTRB(
mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY);
@@ -185,6 +225,22 @@ private:
, mGainmapBRD(std::move(gainmapBRD))
, mGainmapInfo(info) {}
+ SkRect scale(SkRect rect, float scale) const {
+ rect.fLeft *= scale;
+ rect.fTop *= scale;
+ rect.fRight *= scale;
+ rect.fBottom *= scale;
+ return rect;
+ }
+
+ SkIRect scale(SkIRect rect, float scale) const {
+ rect.fLeft *= scale;
+ rect.fTop *= scale;
+ rect.fRight *= scale;
+ rect.fBottom *= scale;
+ return rect;
+ }
+
std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
SkGainmapInfo mGainmapInfo;
@@ -376,6 +432,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
recycledBitmap->setGainmap(std::move(gainmap));
}
bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
+ uirenderer::logBitmapDecode(*recycledBitmap);
return javaBitmap;
}
@@ -392,12 +449,14 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in
hardwareBitmap->setGainmap(std::move(gm));
}
}
+ uirenderer::logBitmapDecode(*hardwareBitmap);
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
if (hasGainmap && heapBitmap != nullptr) {
heapBitmap->setGainmap(std::move(gainmap));
}
+ uirenderer::logBitmapDecode(*heapBitmap);
return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
}
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 258bf91f2124..a210ddf54b2e 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -750,7 +750,7 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() {
std::optional<SkRect> RecyclingClippingPixelAllocator::getSourceBoundsForUpsample(
std::optional<SkRect> subset) {
- if (!uirenderer::Properties::resampleGainmapRegions || !subset || subset->isEmpty()) {
+ if (!uirenderer::Properties::resampleGainmapRegions() || !subset || subset->isEmpty()) {
return std::nullopt;
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index aebc4db37898..90fd3d87cba7 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -37,6 +37,7 @@
#include <hwui/Bitmap.h>
#include <hwui/ImageDecoder.h>
#include <sys/stat.h>
+#include <utils/StatsUtils.h>
#include "Bitmap.h"
#include "BitmapFactory.h"
@@ -485,6 +486,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
hwBitmap->setGainmap(std::move(gm));
}
}
+ uirenderer::logBitmapDecode(*hwBitmap);
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
}
@@ -498,6 +500,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
nativeBitmap->setImmutable();
}
+
+ uirenderer::logBitmapDecode(*nativeBitmap);
return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
ninePatchInsets);
}
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 70e6beda6cb9..5f693462af91 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -86,7 +86,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
overallDescent = std::max(overallDescent, extent.descent);
}
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
uint32_t runCount = layout.getFontRunCount();
std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds;
@@ -229,7 +229,7 @@ float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin:
// CriticalNative
static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
return std::isnan(value) ? NO_OVERRIDE : value;
@@ -241,7 +241,7 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon
// CriticalNative
static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
return std::isnan(value) ? NO_OVERRIDE : value;
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index 2414299321a9..b5591941453d 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -67,6 +67,7 @@ LIBHWUI_PLATFORM {
SkFILEStream::SkFILEStream*;
SkImageInfo::*;
SkMemoryStream::SkMemoryStream*;
+ android::uirenderer::logBitmapDecode*;
};
local:
*;
diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp
new file mode 100644
index 000000000000..5c4027e1a846
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef __ANDROID__
+#include <dlfcn.h>
+#include <log/log.h>
+#include <statslog_hwui.h>
+#include <statssocket_lazy.h>
+#include <utils/Errors.h>
+
+#include <mutex>
+#endif
+
+#include <unistd.h>
+
+#include "StatsUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+#ifdef __ANDROID__
+
+namespace {
+
+int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) {
+ switch (transferType) {
+ case skcms_TFType_sRGBish:
+ return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH;
+ case skcms_TFType_PQish:
+ return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH;
+ case skcms_TFType_HLGish:
+ return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH;
+ default:
+ return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN;
+ }
+}
+
+int32_t toStatsBitmapFormat(SkColorType type) {
+ switch (type) {
+ case kAlpha_8_SkColorType:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8;
+ case kRGB_565_SkColorType:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565;
+ case kN32_SkColorType:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888;
+ case kRGBA_F16_SkColorType:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102;
+ default:
+ return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN;
+ }
+}
+
+} // namespace
+
+#endif
+
+void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) {
+#ifdef __ANDROID__
+
+ if (!statssocket::lazy::IsAvailable()) {
+ std::once_flag once;
+ std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); });
+ return;
+ }
+
+ skcms_TFType tfnType = skcms_TFType_Invalid;
+
+ if (info.colorSpace()) {
+ skcms_TransferFunction tfn;
+ info.colorSpace()->transferFn(&tfn);
+ tfnType = skcms_TransferFunction_getType(&tfn);
+ }
+
+ auto status =
+ stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()),
+ uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap,
+ uirenderer::toStatsBitmapFormat(info.colorType()));
+ ALOGW_IF(status != OK, "Image decoding logging dropped!");
+#endif
+}
+
+void logBitmapDecode(const Bitmap& bitmap) {
+ logBitmapDecode(bitmap.info(), bitmap.hasGainmap());
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h
new file mode 100644
index 000000000000..0c247014a8eb
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <SkBitmap.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <cutils/compiler.h>
+#include <hwui/Bitmap.h>
+
+namespace android {
+namespace uirenderer {
+
+ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap);
+
+ANDROID_API void logBitmapDecode(const Bitmap& bitmap);
+
+} // namespace uirenderer
+} // namespace android