summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/ExtensionHelper.java15
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java28
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java76
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java11
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java39
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp36
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.pngbin57190 -> 57419 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.pngbin57414 -> 57670 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.pngbin53224 -> 56970 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.pngbin53224 -> 57139 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt25
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt57
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrixTest.kt53
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt293
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml7
-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.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml7
-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.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml4
-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.xml6
-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.xml4
-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.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml7
-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.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml4
-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.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt74
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt116
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java21
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt109
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java398
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt260
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt187
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java113
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java113
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java31
-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/DesktopStatusBarInputLayerSupplier.kt117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt92
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt109
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt360
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt297
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt177
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt231
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt119
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java124
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java144
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java185
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java276
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt198
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt21
-rw-r--r--libs/androidfw/PngCrunch.cpp3
-rw-r--r--libs/androidfw/include/androidfw/Png.h2
-rw-r--r--libs/appfunctions/api/current.txt23
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java73
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java31
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java142
-rw-r--r--libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt8
-rw-r--r--libs/hwui/Android.bp2
-rw-r--r--libs/hwui/ColorFilter.h31
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig15
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp22
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.h5
-rw-r--r--libs/hwui/hwui/Bitmap.cpp74
-rw-r--r--libs/hwui/hwui/Bitmap.h23
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp4
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp14
-rw-r--r--libs/hwui/jni/Bitmap.cpp18
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp4
-rw-r--r--libs/hwui/jni/BitmapRegionDecoder.cpp4
-rw-r--r--libs/hwui/jni/ColorFilter.cpp93
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp4
-rw-r--r--libs/hwui/jni/RuntimeEffectUtils.cpp101
-rw-r--r--libs/hwui/jni/RuntimeEffectUtils.h37
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp10
-rw-r--r--libs/hwui/libhwui.map.txt1
l---------libs/hwui/platform/host/android/api-level.h1
-rw-r--r--libs/hwui/utils/StatsUtils.cpp102
-rw-r--r--libs/hwui/utils/StatsUtils.h33
237 files changed, 6321 insertions, 1756 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ExtensionHelper.java
index f466d603bda3..19f837c8bd13 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/ExtensionHelper.java
@@ -23,7 +23,6 @@ import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
-import android.hardware.display.DisplayManagerGlobal;
import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -31,7 +30,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.UiContext;
-import androidx.annotation.VisibleForTesting;
/**
* Util class for both Sidecar and Extensions.
@@ -46,24 +44,15 @@ public final class ExtensionHelper {
* Rotates the input rectangle specified in default display orientation to the current display
* rotation.
*
- * @param displayId the display id.
+ * @param displayInfo the display information.
* @param rotation the target rotation relative to the default display orientation.
* @param inOutRect the input/output Rect as specified in the default display orientation.
*/
- public static void rotateRectToDisplayRotation(
- int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
- final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
- final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
-
- rotateRectToDisplayRotation(displayInfo, rotation, inOutRect);
- }
-
// We suppress the Lint error CheckResult for Rect#intersect because in case the displayInfo and
// folding features are out of sync, e.g. when a foldable devices is unfolding, it is acceptable
// to provide the original folding feature Rect even if they don't intersect.
@SuppressLint("RectIntersectReturnValueIgnored")
- @VisibleForTesting
- static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo,
+ public static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo,
@Surface.Rotation int rotation, @NonNull Rect inOutRect) {
// The inOutRect is specified in the default display orientation, so here we need to get
// the display width and height in the default orientation to perform the intersection and
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index ad194f707cf3..6398c7a2f498 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -39,6 +39,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityThread;
@@ -1394,10 +1395,14 @@ class DividerPresenter implements View.OnTouchListener {
}
private void showVeils(@NonNull SurfaceControl.Transaction t) {
- final Color primaryVeilColor = getContainerBackgroundColor(
- mProperties.mPrimaryContainer, DEFAULT_PRIMARY_VEIL_COLOR);
- final Color secondaryVeilColor = getContainerBackgroundColor(
- mProperties.mSecondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR);
+ final Color primaryVeilColor = getVeilColor(
+ mProperties.mDividerAttributes.getPrimaryVeilColor(),
+ mProperties.mPrimaryContainer,
+ DEFAULT_PRIMARY_VEIL_COLOR);
+ final Color secondaryVeilColor = getVeilColor(
+ mProperties.mDividerAttributes.getSecondaryVeilColor(),
+ mProperties.mSecondaryContainer,
+ DEFAULT_SECONDARY_VEIL_COLOR);
t.setColor(mPrimaryVeil, colorToFloatArray(primaryVeilColor))
.setColor(mSecondaryVeil, colorToFloatArray(secondaryVeilColor))
.setLayer(mDividerSurface, DIVIDER_LAYER)
@@ -1444,6 +1449,21 @@ class DividerPresenter implements View.OnTouchListener {
}
}
+ /**
+ * Returns the veil color.
+ *
+ * If the configured color is not transparent, we use the configured color, otherwise we use
+ * the window background color of the top activity. If the background color of the top
+ * activity is unavailable, the default color is used.
+ */
+ @NonNull
+ private static Color getVeilColor(@ColorInt int configuredColor,
+ @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
+ return configuredColor != Color.TRANSPARENT
+ ? Color.valueOf(configuredColor)
+ : getContainerBackgroundColor(container, defaultColor);
+ }
+
private static float[] colorToFloatArray(@NonNull Color color) {
return new float[]{color.red(), color.green(), color.blue()};
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 556da3798df5..5ce73b908c4c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -34,11 +34,14 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Bundle;
import android.os.IBinder;
import android.os.StrictMode;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.DisplayInfo;
+import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -96,8 +99,29 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final SupportedWindowFeatures mSupportedWindowFeatures;
+ private final DisplayStateProvider mDisplayStateProvider;
+
public WindowLayoutComponentImpl(@NonNull Context context,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
+ this(context, foldingFeatureProducer, new DisplayStateProvider() {
+ @Override
+ public int getDisplayRotation(@NonNull WindowConfiguration windowConfiguration) {
+ return windowConfiguration.getDisplayRotation();
+ }
+
+ @NonNull
+ @Override
+ public DisplayInfo getDisplayInfo(int displayId) {
+ return DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
+ }
+ });
+ }
+
+ @VisibleForTesting
+ WindowLayoutComponentImpl(@NonNull Context context,
+ @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer,
+ @NonNull DisplayStateProvider displayStateProvider) {
+ mDisplayStateProvider = displayStateProvider;
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
mFoldingFeatureProducer = foldingFeatureProducer;
@@ -145,21 +169,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|| containsConsumer(consumer)) {
return;
}
- final IllegalArgumentException exception = new IllegalArgumentException(
- "Context must be a UI Context with display association, which should be"
- + " an Activity, WindowContext or InputMethodService");
- if (!context.isUiContext()) {
- throw exception;
- }
- if (context.getAssociatedDisplayId() == INVALID_DISPLAY) {
- // This is to identify if #isUiContext of a non-UI Context is overridden.
- // #isUiContext is more likely to be overridden than #getAssociatedDisplayId
- // since #isUiContext is a public API.
- StrictMode.onIncorrectContextUsed("The registered Context is a UI Context "
- + "but not associated with any display. "
- + "This Context may not receive any WindowLayoutInfo update. "
- + dumpAllBaseContextToString(context), exception);
- }
+ assertUiContext(context);
Log.d(TAG, "Register WindowLayoutInfoListener on "
+ dumpAllBaseContextToString(context));
mFoldingFeatureProducer.getData((features) -> {
@@ -339,6 +349,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
@Override
@NonNull
public WindowLayoutInfo getCurrentWindowLayoutInfo(@NonNull @UiContext Context context) {
+ assertUiContext(context);
synchronized (mLock) {
return getWindowLayoutInfo(context, mLastReportedFoldingFeatures);
}
@@ -353,6 +364,25 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return mSupportedWindowFeatures;
}
+ private void assertUiContext(@NonNull Context context) {
+ final IllegalArgumentException exception = new IllegalArgumentException(
+ "Context must be a UI Context with display association, which should be "
+ + "an Activity, WindowContext or InputMethodService");
+ if (!context.isUiContext()) {
+ throw exception;
+ }
+ if (context.getAssociatedDisplayId() == INVALID_DISPLAY) {
+ // This is to identify if #isUiContext of a non-UI Context is overridden.
+ // #isUiContext is more likely to be overridden than #getAssociatedDisplayId
+ // since #isUiContext is a public API.
+ StrictMode.onIncorrectContextUsed("The given context is a UI context, "
+ + "but it is not associated with any display. "
+ + "This context may not receive WindowLayoutInfo updates and "
+ + "may get an empty WindowLayoutInfo return value. "
+ + dumpAllBaseContextToString(context), exception);
+ }
+ }
+
/** @see #getWindowLayoutInfo(Context, List) */
private WindowLayoutInfo getWindowLayoutInfo(int displayId,
@NonNull WindowConfiguration windowConfiguration,
@@ -401,15 +431,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
// We will transform the feature bounds to the Activity window, so using the rotation
// from the same source (WindowConfiguration) to make sure they are synchronized.
- final int rotation = windowConfiguration.getDisplayRotation();
+ final int rotation = mDisplayStateProvider.getDisplayRotation(windowConfiguration);
+ final DisplayInfo displayInfo = mDisplayStateProvider.getDisplayInfo(displayId);
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
}
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, rotation, featureRect);
+ final Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayInfo, rotation, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
if (isZero(featureRect)) {
@@ -530,4 +561,13 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
public void onLowMemory() {
}
}
+
+ @VisibleForTesting
+ interface DisplayStateProvider {
+ @Surface.Rotation
+ int getDisplayRotation(@NonNull WindowConfiguration windowConfiguration);
+
+ @NonNull
+ DisplayInfo getDisplayInfo(int displayId);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
index 6e0e7115cfb1..3b30f1b591c0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
@@ -24,7 +24,9 @@ import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityThread;
import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
+import android.view.DisplayInfo;
import androidx.window.common.layout.CommonFoldingFeature;
@@ -96,13 +98,18 @@ class SidecarHelper {
return Collections.emptyList();
}
- final List<SidecarDisplayFeature> features = new ArrayList<>();
+ // We will transform the feature bounds to the Activity window, so using the rotation
+ // from the same source (WindowConfiguration) to make sure they are synchronized.
final int rotation = activity.getResources().getConfiguration().windowConfiguration
.getDisplayRotation();
+ final DisplayInfo displayInfo =
+ DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
+
+ final List<SidecarDisplayFeature> features = new ArrayList<>();
for (CommonFoldingFeature baseFeature : featureList) {
final SidecarDisplayFeature feature = new SidecarDisplayFeature();
final Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, rotation, featureRect);
+ rotateRectToDisplayRotation(displayInfo, rotation, featureRect);
transformToWindowSpaceRect(activity, featureRect);
feature.setRect(featureRect);
feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
index ed4eddf7c209..0643febb79bf 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
@@ -26,6 +26,8 @@ import android.content.ContextWrapper;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
@@ -72,6 +74,11 @@ public class WindowLayoutComponentImplTest {
mWindowLayoutComponent.onDisplayFeaturesChanged(Collections.emptyList());
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddWindowLayoutListener_nonUiContext_throwsError() {
+ mWindowLayoutComponent.addWindowLayoutInfoListener(mAppContext, info -> {});
+ }
+
@Test
public void testGetCurrentWindowLayoutInfo_noFoldingFeature_returnsEmptyList() {
final Context testUiContext = new TestUiContext(mAppContext);
@@ -88,6 +95,29 @@ public class WindowLayoutComponentImplTest {
final WindowConfiguration windowConfiguration =
testUiContext.getResources().getConfiguration().windowConfiguration;
final Rect featureRect = windowConfiguration.getBounds();
+ // Mock DisplayStateProvider to control rotation and DisplayInfo, preventing dependency on
+ // the real device orientation or display configuration. This improves test reliability on
+ // devices like foldables or tablets that might have varying configurations.
+ final WindowLayoutComponentImpl.DisplayStateProvider displayStateProvider =
+ new WindowLayoutComponentImpl.DisplayStateProvider() {
+ @Override
+ public int getDisplayRotation(
+ @NonNull WindowConfiguration windowConfiguration) {
+ return Surface.ROTATION_0;
+ }
+
+ @NonNull
+ @Override
+ public DisplayInfo getDisplayInfo(int displayId) {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = featureRect.width();
+ displayInfo.logicalHeight = featureRect.height();
+ return displayInfo;
+ }
+ };
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(mAppContext,
+ mock(DeviceStateManagerFoldingFeatureProducer.class),
+ displayStateProvider);
final CommonFoldingFeature foldingFeature = new CommonFoldingFeature(
CommonFoldingFeature.COMMON_TYPE_HINGE,
CommonFoldingFeature.COMMON_STATE_FLAT,
@@ -102,12 +132,9 @@ public class WindowLayoutComponentImplTest {
featureRect, FoldingFeature.TYPE_HINGE, FoldingFeature.STATE_FLAT));
}
- @Test
- public void testGetCurrentWindowLayoutInfo_nonUiContext_returnsEmptyList() {
- final WindowLayoutInfo layoutInfo =
- mWindowLayoutComponent.getCurrentWindowLayoutInfo(mAppContext);
-
- assertThat(layoutInfo.getDisplayFeatures()).isEmpty();
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetCurrentWindowLayoutInfo_nonUiContext_throwsError() {
+ mWindowLayoutComponent.getCurrentWindowLayoutInfo(mAppContext);
}
/**
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index 61c09f2c396b..7f54c75929fb 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -22,42 +22,6 @@ package {
default_team: "trendy_team_multitasking_windowing",
}
-android_app {
- name: "WMShellRobolectricScreenshotTestApp",
- platform_apis: true,
- certificate: "platform",
- static_libs: [
- "WindowManager-Shell",
- "platform-screenshot-diff-core",
- "ScreenshotComposeUtilsLib", // ComposableScreenshotTestRule & Theme.PlatformUi.Screenshot
- "SystemUI-res", // Theme.SystemUI (dragged in by ScreenshotComposeUtilsLib)
- ],
- asset_dirs: ["goldens/robolectric"],
- manifest: "AndroidManifestRobolectric.xml",
- use_resource_processor: true,
-}
-
-android_robolectric_test {
- name: "WMShellRobolectricScreenshotTests",
- instrumentation_for: "WMShellRobolectricScreenshotTestApp",
- upstream: true,
- java_resource_dirs: [
- "robolectric/config",
- ],
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "junit",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "truth",
- "platform-parametric-runner-lib",
- ],
- auto_gen_config: true,
-}
-
android_test {
name: "WMShellMultivalentScreenshotTestsOnDevice",
srcs: [
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
index 5b429c0eaf7c..15af624b8609 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
index 6028fa21a8fd..85ce24bea51a 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
index e540b455028b..a1d0e7ba9453 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
index e540b455028b..3bc2ae7dfe73 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
Binary files differ
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 52ce8cb5c0dc..0b515f590f98 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
@@ -23,14 +23,15 @@ import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
import android.view.WindowManager
-import android.view.WindowManagerGlobal
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
@@ -41,6 +42,7 @@ import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
@@ -51,9 +53,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -72,7 +72,7 @@ class BubbleStackViewTest {
private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
private lateinit var bubbleStackView: BubbleStackView
private lateinit var shellExecutor: ShellExecutor
- private lateinit var windowManager: IWindowManager
+ private lateinit var windowManager: WindowManager
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
@@ -83,9 +83,8 @@ class BubbleStackViewTest {
PhysicsAnimatorTestUtils.prepareForTest()
// Disable protolog tool when running the tests from studio
ProtoLog.REQUIRE_PROTOLOGTOOL = false
- windowManager = WindowManagerGlobal.getWindowManagerService()!!
shellExecutor = TestShellExecutor()
- val windowManager = context.getSystemService(WindowManager::class.java)
+ windowManager = context.getSystemService(WindowManager::class.java)
iconFactory =
BubbleIconFactory(
context,
@@ -354,6 +353,16 @@ class BubbleStackViewTest {
assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
}
+ @Test
+ fun removeFromWindow_stopMonitoringSwipeUpGesture() {
+ spyOn(bubbleStackView)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // No way to add to window in the test environment right now so just pretend
+ bubbleStackView.onDetachedFromWindow()
+ }
+ verify(bubbleStackView).stopMonitoringSwipeUpGesture()
+ }
+
private fun createAndInflateChatBubble(key: String): Bubble {
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
new file mode 100644
index 000000000000..cb6fb62bf89b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.bubbles
+
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
+import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+
+/** Helper to create a [Bubble] instance */
+class FakeBubbleFactory {
+
+ companion object {
+
+ fun createViewInfo(bubbleExpandedView: BubbleBarExpandedView): BubbleViewInfo {
+ return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView }
+ }
+
+ fun createChatBubbleWithViewInfo(
+ context: Context,
+ key: String = "key",
+ viewInfo: BubbleViewInfo,
+ ): Bubble {
+ val bubble =
+ Bubble(
+ key,
+ ShortcutInfo.Builder(context, "id").build(),
+ 100, /* desiredHeight */
+ Resources.ID_NULL, /* desiredHeightResId */
+ "title",
+ 0, /* taskId */
+ null, /* locus */
+ true, /* isDismissable */
+ directExecutor(),
+ directExecutor(),
+ ) {}
+ bubble.setViewInfo(viewInfo)
+ return bubble
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrixTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrixTest.kt
new file mode 100644
index 000000000000..3ed360461c25
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrixTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.bubbles.animation
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [AnimatableScaleMatrix] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnimatableScaleMatrixTest {
+
+ @Test
+ fun test_equals_matricesWithSameValuesAreNotEqual() {
+ val matrix1 = AnimatableScaleMatrix().apply { setScale(0.5f, 0.5f) }
+ val matrix2 = AnimatableScaleMatrix().apply { setScale(0.5f, 0.5f) }
+ assertThat(matrix1).isNotEqualTo(matrix2)
+ }
+
+ @Test
+ fun test_hashCode_remainsSameIfMatrixUpdates() {
+ val matrix = AnimatableScaleMatrix().apply { setScale(0.5f, 0.5f) }
+ val hash1 = matrix.hashCode()
+ matrix.setScale(0.75f, 0.75f)
+ val hash2 = matrix.hashCode()
+
+ assertThat(hash1).isEqualTo(hash2)
+ }
+
+ @Test
+ fun test_hashCode_matricesWithSameValuesHaveDiffHashCode() {
+ val matrix1 = AnimatableScaleMatrix().apply { setScale(0.5f, 0.5f) }
+ val matrix2 = AnimatableScaleMatrix().apply { setScale(0.5f, 0.5f) }
+ assertThat(matrix1.hashCode()).isNotEqualTo(matrix2.hashCode())
+ }
+}
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
new file mode 100644
index 000000000000..2fbf089d99d6
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -0,0 +1,293 @@
+/*
+ * 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.bubbles.bar
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.os.Handler
+import android.os.UserManager
+import android.view.IWindowManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.ProtoLog
+import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.Bubble
+import com.android.wm.shell.bubbles.BubbleController
+import com.android.wm.shell.bubbles.BubbleData
+import com.android.wm.shell.bubbles.BubbleDataRepository
+import com.android.wm.shell.bubbles.BubbleEducationController
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager
+import com.android.wm.shell.bubbles.BubbleLogger
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.BubbleTaskView
+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.properties.BubbleProperties
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskView
+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 java.util.Collections
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Tests for [BubbleBarLayerView] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleBarLayerViewTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ private lateinit var bubbleBarLayerView: BubbleBarLayerView
+
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+
+ private lateinit var bubble: Bubble
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ val bubbleLogger = BubbleLogger(uiEventLoggerFake)
+
+ val mainExecutor = TestExecutor()
+ val bgExecutor = TestExecutor()
+
+ val windowManager = context.getSystemService(WindowManager::class.java)
+
+ val bubblePositioner = BubblePositioner(context, windowManager)
+ bubblePositioner.setShowingInBubbleBar(true)
+
+ val bubbleData =
+ BubbleData(
+ context,
+ bubbleLogger,
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor,
+ bgExecutor,
+ )
+
+ val bubbleController =
+ createBubbleController(
+ bubbleData,
+ windowManager,
+ bubbleLogger,
+ bubblePositioner,
+ mainExecutor,
+ bgExecutor,
+ )
+ bubbleController.asBubbles().setSysuiProxy(mock(SysuiProxy::class.java))
+ // Flush so that proxy gets set
+ mainExecutor.flushAll()
+
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger)
+
+ val expandedViewManager = createExpandedViewManager()
+ val bubbleTaskView = FakeBubbleTaskViewFactory(mainExecutor).create()
+ val bubbleBarExpandedView =
+ (LayoutInflater.from(context)
+ .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */)
+ as BubbleBarExpandedView)
+ .apply {
+ initialize(
+ expandedViewManager,
+ bubblePositioner,
+ bubbleLogger,
+ false /* isOverflow */,
+ bubbleTaskView,
+ mainExecutor,
+ bgExecutor,
+ null, /* regionSamplingProvider */
+ )
+ }
+
+ val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
+ bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
+ }
+
+ private fun createBubbleController(
+ bubbleData: BubbleData,
+ windowManager: WindowManager?,
+ bubbleLogger: BubbleLogger,
+ bubblePositioner: BubblePositioner,
+ mainExecutor: TestExecutor,
+ bgExecutor: TestExecutor,
+ ): BubbleController {
+ val shellInit = ShellInit(mainExecutor)
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController =
+ ShellController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ mock<DisplayInsetsController>(),
+ mainExecutor,
+ )
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ val bubbleDataRepository =
+ BubbleDataRepository(
+ mock<LauncherApps>(),
+ mainExecutor,
+ bgExecutor,
+ BubblePersistentRepository(context),
+ )
+
+ return BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleDataRepository,
+ mock<IStatusBarService>(),
+ windowManager,
+ WindowManagerShellWrapper(mainExecutor),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ bubbleLogger,
+ mock<TaskStackListenerImpl>(),
+ mock<ShellTaskOrganizer>(),
+ bubblePositioner,
+ mock<DisplayController>(),
+ null,
+ null,
+ mainExecutor,
+ mock<Handler>(),
+ bgExecutor,
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ SyncTransactionQueue(TransactionPool(), mainExecutor),
+ mock<IWindowManager>(),
+ mock<BubbleProperties>(),
+ )
+ }
+
+ @Test
+ fun testEventLogging_dismissExpandedViewViaDrag() {
+ getInstrumentation().runOnMainSync { bubbleBarLayerView.showExpandedView(bubble) }
+ assertThat(bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)).isNotNull()
+
+ bubbleBarLayerView.dragController?.dragListener?.onReleased(true)
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) :
+ BubbleTaskViewFactory {
+ override fun create(): BubbleTaskView {
+ val taskViewTaskController = mock<TaskViewTaskController>()
+ val taskView = TaskView(context, taskViewTaskController)
+ val taskInfo = mock<ActivityManager.RunningTaskInfo>()
+ whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
+ return BubbleTaskView(taskView, mainExecutor)
+ }
+ }
+
+ private fun createExpandedViewManager(): BubbleExpandedViewManager {
+ return object : BubbleExpandedViewManager {
+ override val overflowBubbles: List<Bubble>
+ get() = Collections.emptyList()
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {}
+
+ override fun collapseStack() {}
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {}
+
+ override fun removeBubble(key: String, reason: Int) {}
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {}
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+
+ override fun isStackExpanded(): Boolean {
+ return true
+ }
+
+ override fun isShowingAsBubbleBar(): Boolean {
+ return true
+ }
+
+ override fun hideCurrentInputMethod() {}
+
+ override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ }
+ }
+
+ private class TestExecutor : ShellExecutor {
+
+ private val runnables: MutableList<Runnable> = mutableListOf()
+
+ override fun execute(runnable: Runnable) {
+ runnables.add(runnable)
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ execute(runnable)
+ }
+
+ override fun removeCallbacks(runnable: Runnable?) {}
+
+ override fun hasCallback(runnable: Runnable?): Boolean = false
+
+ fun flushAll() {
+ while (runnables.isNotEmpty()) {
+ runnables.removeAt(0).run()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 4a42616a45ec..4c7d1c7339fb 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -22,7 +22,6 @@
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/bubble_popup_margin_top"
android:layout_marginHorizontal="@dimen/bubble_popup_margin_horizontal"
- android:layout_marginBottom="@dimen/bubble_popup_margin_bottom"
android:elevation="@dimen/bubble_popup_elevation"
android:gravity="center_horizontal"
android:orientation="vertical">
@@ -30,7 +29,7 @@
<ImageView
android:layout_width="@dimen/bubble_popup_icon_size"
android:layout_height="@dimen/bubble_popup_icon_size"
- android:tint="?androidprv:attr/materialColorPrimary"
+ android:tint="?androidprv:attr/materialColorOutline"
android:contentDescription="@null"
android:src="@drawable/pip_ic_settings"/>
@@ -49,6 +48,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
+ android:paddingBottom="@dimen/bubble_popup_padding_bottom"
android:maxWidth="@dimen/bubble_popup_content_max_width"
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index f19c3c762d9d..345c399652f9 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -22,7 +22,6 @@
android:layout_gravity="bottom|end"
android:layout_marginTop="@dimen/bubble_popup_margin_top"
android:layout_marginHorizontal="@dimen/bubble_popup_margin_horizontal"
- android:layout_marginBottom="@dimen/bubble_popup_margin_bottom"
android:elevation="@dimen/bubble_popup_elevation"
android:gravity="center_horizontal"
android:orientation="vertical">
@@ -49,6 +48,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
+ android:paddingBottom="@dimen/bubble_popup_padding_bottom"
android:maxWidth="@dimen/bubble_popup_content_max_width"
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index 62782a784db9..e7ead63a8df6 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -22,14 +22,14 @@
android:gravity="bottom|end">
<include android:id="@+id/size_compat_hint"
- android:visibility="gone"
+ android:visibility="invisible"
android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
<ImageButton
android:id="@+id/size_compat_restart_button"
- android:visibility="gone"
+ android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
index 433d8546ece0..b5f04c3b815a 100644
--- a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -22,14 +22,14 @@
android:gravity="bottom|end">
<include android:id="@+id/user_aspect_ratio_settings_hint"
- android:visibility="gone"
+ android:visibility="invisible"
android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
<ImageButton
android:id="@+id/user_aspect_ratio_settings_button"
- android:visibility="gone"
+ android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index b29e8bf5e922..95c2bb59d202 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index d96033f286f8..ba74e342f353 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 42ef4c307b1e..a8febc80ffc1 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 8e9c7045118f..8c924e342875 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -133,9 +133,10 @@
<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>
+ <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>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
- <skip />
+ <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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"সোঁফাললৈ স্নেপ কৰক"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফ’ল্ট ছেটিং খোলক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 1310f6f743f4..aa232e330604 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -133,6 +133,10 @@
<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_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>
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 b9c42397f8c7..256344a4cb31 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 5d389d849433..701c51091aa4 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 807878620d90..9ab86f4cbc56 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 8db7144e6db6..22a445f1754c 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -133,9 +133,10 @@
<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>
+ <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>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
- <skip />
+ <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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ডানদিকে স্ন্যাপ করুন"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফল্ট হিসেবে থাকা সেটিংস খুলুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 3d922d8334c8..73f30d797883 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -133,6 +133,8 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index dc96cd0f72ec..499ed329e511 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 30ba78ad8e06..6a5780e01822 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovat"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnovit"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Přichytit vlevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 433b858cfd84..430cf96cd72f 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 0b4d189716c2..cafaa89f57e3 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximieren"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Rechts andocken"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Einstellungen für die Option „Standardmäßig öffnen“"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index beff019e6897..d02fae2a986d 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 01d3d25e7e4f..f9911451f4b5 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 20ec076a33d1..2d123ec3a3d4 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap Screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximize"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 01d3d25e7e4f..f9911451f4b5 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 01d3d25e7e4f..f9911451f4b5 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index c9e7ecbb056e..210b708b49af 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 903294bbd1cf..3c7bfe5a3cb4 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f6bebff9a4f3..d17ee02a3a7f 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 267452f2e6e1..f9419bc4614b 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index ec4779b0a129..a3d3cbc872fd 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 6dcd20cc0227..ee5dd6516098 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index df6d503c2121..dc4789169146 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 3a7f2f0bd4fa..a52ab49da3ab 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index cb7bb324de27..97d5e51e5b98 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index bee06fed5fd1..362ff8d874bb 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"મોટું કરો"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"જમણે સ્નૅપ કરો"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"\'ડિફૉલ્ટ તરીકે ખોલો\' સેટિંગ"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 2c141996b109..527793eac9c3 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index a2a52d13b463..659d1ec39b73 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Izradi snimku zaslona"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti 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_maximize_button_text" msgid="3090199175564175845">"Maksimiziraj"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vrati"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Poravnaj lijevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 03cc9f569d29..943b5eb30768 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 28c762eb0752..6bcfc9a22d6e 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 4929b79875a0..96a3ebce9a45 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index d7ba53f73926..ca1bc15edf33 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Smelluskjár"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Stækka"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Endurheimta"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Smella til vinstri"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 4522d37d289a..87919b5dc1ff 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aggancia schermo"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ingrandisci"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Ripristina"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Aggancia a sinistra"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 110d357d0cb2..17ffe8ee7600 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -133,12 +133,16 @@
<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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"הצמדה לימין"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"הגדרות לפתיחה כברירת מחדל"</string>
- <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"בחירת האופן שבו קישורים לדפי אינטרנט אחרים ייפתחו באפליקציה הזו"</string>
+ <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"כאן בוחרים איך לפתוח באפליקציה הזו קישורים לדפי אינטרנט"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 69421da5b450..c7a77d9b9214 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 46859889304a..39362ef4ca57 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 84e7ea5b7af3..45f85b9e9a70 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 7eb81e158513..9c4ae05f3a36 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 2b43f573e1ab..f365cfb34412 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 8f36aab7ce8a..2bf1b05a919b 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index c1219ba86249..392ae4cab107 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -133,12 +133,16 @@
<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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Оңго жылдыруу"</string>
- <string name="open_by_default_settings_text" msgid="2526548548598185500">"Демейки шартта ачуу параметрлери"</string>
- <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Колдонмодо шилтемелер кантип ачылышы керек экенин тандаңыз"</string>
+ <string name="open_by_default_settings_text" msgid="2526548548598185500">"Демейки шартта ачылуучу шилтемелердин параметрлери"</string>
+ <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Колдонмодо шилтемелер кантип ачыларын тандаңыз"</string>
<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>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 375fc6435935..4e4b678755b2 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ແນບຂວາ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ເປີດຕາມການຕັ້ງຄ່າເລີ່ມຕົ້ນ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index dfc3b45786de..5a7f58e5781a 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Padidinti"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Atkurti"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pritraukti kairėje"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 87818524cabe..60912f627841 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 88fed7414758..7c0c856ed1a7 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 71fb78eca0f3..e14ab8b0161c 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 04f2f8202961..d406b99e80b3 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Томруулах"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Баруун тийш зэрэгцүүлэх"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Өгөгдмөл тохиргоогоор нээх"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index be1df3273b21..871bc3fcc8e7 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 04da8869d5a7..71666cae93c8 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</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">"Maksimumkan"</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">"Autojajar ke kiri"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 915a7cd3d67a..ae34624c98a0 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 8e5aee1eb49b..9270dc859728 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 42f2336c63ff..7015b2c11b32 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index d19a4d44d6f9..45305d62a69b 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index f67a6b2b7d38..2d30441ab6f4 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ବଡ଼ କରନ୍ତୁ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ଡାହାଣରେ ସ୍ନାପ କରନ୍ତୁ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ଡିଫଲ୍ଟ ସେଟିଂସକୁ ଖୋଲନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 76d59af75bfb..26ba461cba5d 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ਵੱਡਾ ਕਰੋ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ਸੱਜੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੈਟਿੰਗਾਂ ਮੁਤਾਬਕ ਖੋਲ੍ਹੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 502e53abdcd3..5f78b134ad22 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksymalizuj"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Przywróć"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Przyciągnij do lewej"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 3ec5e76bc5b4..8c7f9e73296d 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 184a5b336fd8..cd78ef95d88e 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar ecrã"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
+ <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">"Encaixar à esquerda"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 3ec5e76bc5b4..8c7f9e73296d 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 9703328d4d98..e3fe2804bdcd 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 401a8aab2576..442fca3ef0a7 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index c101b4cd757c..8a7ad3b9f80c 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"විහිදන්න"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"දකුණට ස්නැප් කරන්න"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"පෙරනිමි සැකසීම් මඟින් විවෘත කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 7214300f1eb7..4234e8073bc8 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 04fe7e89c16e..ae7e524da6cc 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</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">"Maksimiraj"</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">"Pripni levo"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 6662ca1a059d..de6f681cfe74 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -133,9 +133,12 @@
<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>
- <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizo"</string>
- <!-- no translation found for desktop_mode_maximize_menu_restore_button_text (4234449220944704387) -->
+ <!-- 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_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>
<string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Zhvendos djathtas"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Hap sipas cilësimeve të parazgjedhura"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index d0a4ae684af7..901d6d967a7d 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 6ce2a9a1cb4d..6566801b7c63 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 40967f02be45..a952011385de 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 3140c2c77fae..2c73d3a14620 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 62e62c7a1e25..b17d4d1afaf7 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index e6386a20e492..43cee41f5a15 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 176be336117d..428499532005 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -133,6 +133,8 @@
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"I-snap ang Screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
+ <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+ <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string>
<string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"I-maximize"</string>
<string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"I-restore"</string>
<string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"I-snap pakaliwa"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 73248e3d0c96..7eac4a8e4ffb 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index a655a3eb452c..5fb14bf50e10 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 4bdea83e59e3..bb0358f12b7a 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index b3a496f27582..0648dd1c1bb8 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 36d66e432590..dda2225b5f3e 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 64446cd4b342..2fb3f5ab5ea4 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -133,6 +133,8 @@
<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>
+ <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>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 4970e8b92afb..1d7fb4c4c6e2 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index fcdcccaee5f5..8083e378bf29 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index cbc6c022a3c6..092efd6593fc 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -133,6 +133,10 @@
<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_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>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 6cd380d1eed9..fa1aa193e1e3 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -239,11 +239,11 @@
<!-- Max width for the bubble popup view. -->
<dimen name="bubble_popup_content_max_width">300dp</dimen>
<!-- Horizontal margin for the bubble popup view. -->
- <dimen name="bubble_popup_margin_horizontal">32dp</dimen>
+ <dimen name="bubble_popup_margin_horizontal">24dp</dimen>
<!-- Top margin for the bubble bar education views. -->
<dimen name="bubble_popup_margin_top">24dp</dimen>
- <!-- Bottom margin for the bubble bar education views. -->
- <dimen name="bubble_popup_margin_bottom">32dp</dimen>
+ <!-- Bottom padding for the bubble bar education views. -->
+ <dimen name="bubble_popup_padding_bottom">8dp</dimen>
<!-- Text margin for the bubble bar education views. -->
<dimen name="bubble_popup_text_margin">16dp</dimen>
<!-- Size of icons in the bubble bar education views. -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt
new file mode 100644
index 000000000000..0586e265eced
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.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.shared.animation
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.util.DisplayMetrics
+import android.view.SurfaceControl.Transaction
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.window.TransitionInfo.Change
+
+/** Creates minimization animation */
+object MinimizeAnimator {
+
+ private const val MINIMIZE_ANIM_ALPHA_DURATION_MS = 100L
+
+ private val STANDARD_ACCELERATE = PathInterpolator(0.3f, 0f, 1f, 1f)
+
+ private val minimizeBoundsAnimationDef =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 200,
+ endOffsetYDp = 12f,
+ endScale = 0.97f,
+ interpolator = STANDARD_ACCELERATE,
+ )
+
+ @JvmStatic
+ fun create(
+ displayMetrics: DisplayMetrics,
+ change: Change,
+ transaction: Transaction,
+ onAnimFinish: (Animator) -> Unit,
+ ): Animator {
+ val boundsAnimator = WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ minimizeBoundsAnimationDef,
+ change,
+ transaction,
+ )
+ val alphaAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = MINIMIZE_ANIM_ALPHA_DURATION_MS
+ interpolator = LinearInterpolator()
+ addUpdateListener { animation ->
+ transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
+ }
+ }
+ val listener = object : Animator.AnimatorListener {
+ override fun onAnimationEnd(animator: Animator) = onAnimFinish(animator)
+ override fun onAnimationCancel(animator: Animator) = Unit
+ override fun onAnimationRepeat(animator: Animator) = Unit
+ override fun onAnimationStart(animator: Animator) = Unit
+ }
+ return AnimatorSet().apply {
+ playTogether(boundsAnimator, alphaAnimator)
+ addListener(listener)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt
new file mode 100644
index 000000000000..91d66eaeb088
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.shared.animation
+
+import android.animation.PointFEvaluator
+import android.animation.ValueAnimator
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.util.TypedValue
+import android.view.SurfaceControl
+import android.view.animation.Interpolator
+import android.window.TransitionInfo
+
+/** Creates animations that can be applied to windows/surfaces. */
+object WindowAnimator {
+
+ /** Parameters defining a window bounds animation. */
+ data class BoundsAnimationParams(
+ val durationMs: Long,
+ val startOffsetYDp: Float = 0f,
+ val endOffsetYDp: Float = 0f,
+ val startScale: Float = 1f,
+ val endScale: Float = 1f,
+ val interpolator: Interpolator,
+ )
+
+ /**
+ * Creates an animator to reposition and scale the bounds of the leash of the given change.
+ *
+ * @param displayMetrics the metrics of the display where the animation plays in
+ * @param boundsAnimDef the parameters for the animation itself (duration, scale, position)
+ * @param change the change to which the animation should be applied
+ * @param transaction the transaction to apply the animation to
+ */
+ fun createBoundsAnimator(
+ displayMetrics: DisplayMetrics,
+ boundsAnimDef: BoundsAnimationParams,
+ change: TransitionInfo.Change,
+ transaction: SurfaceControl.Transaction,
+ ): ValueAnimator {
+ val startPos =
+ getPosition(
+ displayMetrics,
+ change.endAbsBounds,
+ boundsAnimDef.startScale,
+ boundsAnimDef.startOffsetYDp,
+ )
+ val leash = change.leash
+ val endPos =
+ getPosition(
+ displayMetrics,
+ change.endAbsBounds,
+ boundsAnimDef.endScale,
+ boundsAnimDef.endOffsetYDp,
+ )
+ return ValueAnimator.ofObject(PointFEvaluator(), startPos, endPos).apply {
+ duration = boundsAnimDef.durationMs
+ interpolator = boundsAnimDef.interpolator
+ addUpdateListener { animation ->
+ val animPos = animation.animatedValue as PointF
+ val animScale =
+ interpolate(
+ boundsAnimDef.startScale,
+ boundsAnimDef.endScale,
+ animation.animatedFraction
+ )
+ transaction
+ .setPosition(leash, animPos.x, animPos.y)
+ .setScale(leash, animScale, animScale)
+ .apply()
+ }
+ }
+ }
+
+ private fun interpolate(startVal: Float, endVal: Float, fraction: Float): Float {
+ require(fraction in 0.0f..1.0f)
+ return startVal + (endVal - startVal) * fraction
+ }
+
+ private fun getPosition(
+ displayMetrics: DisplayMetrics,
+ bounds: Rect,
+ scale: Float,
+ offsetYDp: Float
+ ) = PointF(bounds.left.toFloat(), bounds.top.toFloat()).apply {
+ check(scale in 0.0f..1.0f)
+ // Scale the bounds down with an anchor in the center
+ offset(
+ (bounds.width().toFloat() * (1 - scale) / 2),
+ (bounds.height().toFloat() * (1 - scale) / 2),
+ )
+ val offsetYPx =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ offsetYDp,
+ displayMetrics,
+ )
+ .toInt()
+ offset(/* dx= */ 0f, offsetYPx.toFloat())
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 0150bcdbd412..6bc995f14d44 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -102,6 +102,15 @@ public class DesktopModeStatus {
"persist.wm.debug.enter_desktop_by_default_on_freeform_display";
/**
+ * Sysprop declaring whether to enable drag-to-maximize for desktop windows.
+ *
+ * <p>If it is not defined, then {@code R.integer.config_dragToMaximizeInDesktopMode}
+ * is used.
+ */
+ public static final String ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP =
+ "persist.wm.debug.enable_drag_to_maximize";
+
+ /**
* Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time.
*
* <p>If it is not defined, then {@code R.integer.config_maxDesktopWindowingActiveTasks} is
@@ -230,6 +239,18 @@ public class DesktopModeStatus {
R.bool.config_enterDesktopByDefaultOnFreeformDisplay));
}
+ /**
+ * Return {@code true} if a window should be maximized when it's dragged to the top edge of the
+ * screen.
+ */
+ public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) {
+ if (!Flags.enableDragToMaximize()) {
+ return false;
+ }
+ return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP,
+ context.getResources().getBoolean(R.bool.config_dragToMaximizeInDesktopMode));
+ }
+
/** Dumps DesktopModeStatus flags and configs. */
public static void dump(PrintWriter pw, String prefix, Context context) {
String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
index 0e8e90467745..23e7441ff86b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
@@ -15,6 +15,10 @@
*/
package com.android.wm.shell.shared.desktopmode
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
import android.annotation.ColorInt
import android.content.Context
import android.graphics.Bitmap
@@ -23,6 +27,10 @@ import android.graphics.drawable.shapes.RoundRectShape
import android.util.TypedValue
import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceView
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import android.window.TaskSnapshot
@@ -39,7 +47,7 @@ abstract class ManageWindowsViewContainer(
lateinit var menuView: ManageWindowsView
/** Creates the base menu view and fills it with icon views. */
- fun show(snapshotList: List<Pair<Int, TaskSnapshot>>,
+ fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)): ManageWindowsView {
menuView = ManageWindowsView(context, menuBackgroundColor).apply {
@@ -51,11 +59,24 @@ abstract class ManageWindowsViewContainer(
return menuView
}
+ /** Play the animation for opening the menu. */
+ fun animateOpen() {
+ menuView.animateOpen()
+ }
+
+ /**
+ * Play the animation for closing the menu. On finish, will run the provided callback,
+ * which will be responsible for removing the view from the container used in [addToContainer].
+ */
+ fun animateClose() {
+ menuView.animateClose { removeFromContainer() }
+ }
+
/** Adds the menu view to the container responsible for displaying it. */
abstract fun addToContainer(menuView: ManageWindowsView)
- /** Dispose of the menu, perform needed cleanup. */
- abstract fun close()
+ /** Removes the menu view from the container used in the method above */
+ abstract fun removeFromContainer()
companion object {
const val MANAGE_WINDOWS_MINIMUM_INSTANCES = 2
@@ -65,6 +86,8 @@ abstract class ManageWindowsViewContainer(
private val context: Context,
menuBackgroundColor: Int
) {
+ private val animators = mutableListOf<Animator>()
+ private val iconViews = mutableListOf<SurfaceView>()
val rootView: LinearLayout = LinearLayout(context)
var menuHeight = 0
var menuWidth = 0
@@ -147,6 +170,7 @@ abstract class ManageWindowsViewContainer(
menuWidth += (instanceIconWidth + iconMargin).toInt()
}
rowLayout?.addView(appSnapshotButton)
+ iconViews += appSnapshotButton
appSnapshotButton.requestLayout()
rowLayout?.post {
appSnapshotButton.holder.surface
@@ -190,6 +214,78 @@ abstract class ManageWindowsViewContainer(
}
}
+ /** Play the animation for opening the menu. */
+ fun animateOpen() {
+ animateView(rootView, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
+ MENU_START_ALPHA, MENU_FULL_ALPHA)
+ for (view in iconViews) {
+ animateView(view, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
+ MENU_START_ALPHA, MENU_FULL_ALPHA)
+ }
+ createAnimatorSet().start()
+ }
+
+ /** Play the animation for closing the menu. */
+ fun animateClose(callback: () -> Unit) {
+ animateView(rootView, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
+ MENU_FULL_ALPHA, MENU_START_ALPHA)
+ for (view in iconViews) {
+ animateView(view, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
+ MENU_FULL_ALPHA, MENU_START_ALPHA)
+ }
+ createAnimatorSet().apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ callback.invoke()
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun animateView(
+ view: View,
+ startBoundsScale: Float,
+ endBoundsScale: Float,
+ startAlpha: Float,
+ endAlpha: Float) {
+ animators += ObjectAnimator.ofFloat(
+ view,
+ SCALE_X,
+ startBoundsScale,
+ endBoundsScale
+ ).apply {
+ duration = MENU_BOUNDS_ANIM_DURATION
+ }
+ animators += ObjectAnimator.ofFloat(
+ view,
+ SCALE_Y,
+ startBoundsScale,
+ endBoundsScale
+ ).apply {
+ duration = MENU_BOUNDS_ANIM_DURATION
+ }
+ animators += ObjectAnimator.ofFloat(
+ view,
+ ALPHA,
+ startAlpha,
+ endAlpha
+ ).apply {
+ duration = MENU_ALPHA_ANIM_DURATION
+ startDelay = MENU_ALPHA_ANIM_DELAY
+ }
+ }
+
+ private fun createAnimatorSet(): AnimatorSet {
+ val animatorSet = AnimatorSet().apply {
+ playTogether(animators)
+ }
+ animators.clear()
+ return animatorSet
+ }
+
companion object {
private const val MENU_RADIUS_DP = 26f
private const val ICON_WIDTH_DP = 204f
@@ -198,6 +294,13 @@ abstract class ManageWindowsViewContainer(
private const val ICON_MARGIN_DP = 16f
private const val MENU_ELEVATION_DP = 1f
private const val MENU_MAX_ICONS_PER_ROW = 3
+ private const val MENU_BOUNDS_ANIM_DURATION = 200L
+ private const val MENU_BOUNDS_SHRUNK_SCALE = 0.8f
+ private const val MENU_BOUNDS_FULL_SCALE = 1f
+ private const val MENU_ALPHA_ANIM_DURATION = 100L
+ private const val MENU_ALPHA_ANIM_DELAY = 50L
+ private const val MENU_START_ALPHA = 0f
+ private const val MENU_FULL_ALPHA = 1f
}
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index eb7ef1478a90..62ca5c687a2a 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.shared.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
@@ -170,26 +171,34 @@ public abstract class PipContentOverlay {
private final Context mContext;
private final int mAppIconSizePx;
- private final Rect mAppBounds;
+ /**
+ * The bounds of the application window relative to the task leash.
+ */
+ private final Rect mRelativeAppBounds;
private final int mOverlayHalfSize;
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
private Bitmap mBitmap;
- public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
- Drawable appIcon, int appIconSizePx) {
+ // TODO(b/356277166): add non-match_parent support on PIP2.
+ /**
+ * @param context the {@link Context} that contains the icon information
+ * @param relativeAppBounds the bounds of the app window frame relative to the task leash
+ * @param destinationBounds the bounds for rhe PIP task
+ * @param appIcon the app icon {@link Drawable}
+ * @param appIconSizePx the icon dimension in pixel
+ */
+ public PipAppIconOverlay(@NonNull Context context, @NonNull Rect relativeAppBounds,
+ @NonNull Rect destinationBounds, @NonNull Drawable appIcon, int appIconSizePx) {
mContext = context;
final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
- final int overlaySize = getOverlaySize(appBounds, destinationBounds);
+ final int overlaySize = getOverlaySize(relativeAppBounds, destinationBounds);
mOverlayHalfSize = overlaySize >> 1;
-
- // When the activity is in the secondary split, make sure the scaling center is not
- // offset.
- mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
+ mRelativeAppBounds = relativeAppBounds;
mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
@@ -206,9 +215,9 @@ public abstract class PipContentOverlay {
* the overlay will be drawn with the max size of the start and end bounds in different
* rotation.
*/
- public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
- final int appWidth = appBounds.width();
- final int appHeight = appBounds.height();
+ public static int getOverlaySize(Rect overlayBounds, Rect destinationBounds) {
+ final int appWidth = overlayBounds.width();
+ final int appHeight = overlayBounds.height();
return Math.max(Math.max(appWidth, appHeight),
Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
@@ -230,15 +239,15 @@ public abstract class PipContentOverlay {
mTmpTransform.reset();
// In order for the overlay to always cover the pip window, the overlay may have a
// size larger than the pip window. Make sure that app icon is at the center.
- final int appBoundsCenterX = mAppBounds.centerX();
- final int appBoundsCenterY = mAppBounds.centerY();
+ final int appBoundsCenterX = mRelativeAppBounds.centerX();
+ final int appBoundsCenterY = mRelativeAppBounds.centerY();
mTmpTransform.setTranslate(
appBoundsCenterX - mOverlayHalfSize,
appBoundsCenterY - mOverlayHalfSize);
// Scale back the bitmap with the pivot point at center.
final float scale = Math.min(
- (float) mAppBounds.width() / currentBounds.width(),
- (float) mAppBounds.height() / currentBounds.height());
+ (float) mRelativeAppBounds.width() / currentBounds.width(),
+ (float) mRelativeAppBounds.height() / currentBounds.height());
mTmpTransform.postScale(scale, scale, appBoundsCenterX, appBoundsCenterY);
atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
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 5eb5d8962b55..f296c710f9a0 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
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.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -190,6 +191,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Override
public void onResult(@Nullable Bundle result) {
mShellExecutor.execute(() -> {
+ if (mBackGestureStarted && result != null && result.getBoolean(
+ BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED)) {
+ // Host app won't able to process motion event anymore, so pilfer
+ // pointers anyway.
+ if (mBackNavigationInfo != null) {
+ mBackNavigationInfo.disableAppProgressGenerationAllowed();
+ }
+ tryPilferPointers();
+ return;
+ }
if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
// If an uninterruptible animation is already in progress, we should
// ignore this due to it may cause focus lost. (alpha = 0)
@@ -1262,6 +1273,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return handleCloseTransition(info, st, ft, finishCallback);
}
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (transition == mClosePrepareTransition && aborted) {
+ mClosePrepareTransition = null;
+ applyFinishOpenTransition();
+ }
+ }
+
void createClosePrepareTransition() {
if (mClosePrepareTransition != null) {
Log.e(TAG, "Re-create close prepare transition");
@@ -1280,7 +1300,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final TransitionInfo init = mOpenTransitionInfo;
// Find prepare open target
boolean openShowWallpaper = false;
- final ArrayList<OpenChangeInfo> targets = new ArrayList<>();
+ final ArrayList<SurfaceControl> openSurfaces = new ArrayList<>();
int tmpSize;
for (int j = init.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = init.getChanges().get(j);
@@ -1293,13 +1313,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& openToken == null) {
continue;
}
- targets.add(new OpenChangeInfo(openComponent, openTaskId, openToken));
+ openSurfaces.add(change.getLeash());
if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
openShowWallpaper = true;
}
}
}
- if (targets.isEmpty()) {
+ if (openSurfaces.isEmpty()) {
// This shouldn't happen, but if that happen, consume the initial transition anyway.
Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
+ "animated target from the open transition=" + mOpenTransitionInfo);
@@ -1311,7 +1331,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
tmpSize = info.getChanges().size();
for (int j = 0; j < tmpSize; ++j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isOpenChangeMatched(targets, change)) {
+ if (isOpenSurfaceMatched(openSurfaces, change)) {
// This is original close target, potential be close, but cannot determine
// from it.
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
@@ -1324,15 +1344,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
if (!isOpen) {
// Close transition, the transition info should be:
- // init info(open A & wallpaper)
- // current info(close B target)
+ // init info(open A & wallpaper) => init info(open A & change B & wallpaper)
+ // current info(close B target) => current info(change A & close B)
// remove init info(open/change A target & wallpaper)
boolean moveToTop = false;
boolean excludeOpenTarget = false;
boolean mergePredictive = false;
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isOpenChangeMatched(targets, change)) {
+ if (isOpenSurfaceMatched(openSurfaces, change)) {
if (TransitionUtil.isClosingMode(change.getMode())) {
excludeOpenTarget = true;
}
@@ -1353,7 +1373,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (change.hasFlags(FLAG_IS_WALLPAPER)) {
continue;
}
- if (isOpenChangeMatched(targets, change)) {
+ if (isOpenSurfaceMatched(openSurfaces, change)) {
if (excludeOpenTarget) {
// App has triggered another change during predictive back
// transition, filter out predictive back target.
@@ -1388,7 +1408,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (nonBackClose && nonBackOpen) {
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
final TransitionInfo.Change change = info.getChanges().get(j);
- if (isOpenChangeMatched(targets, change)) {
+ if (isOpenSurfaceMatched(openSurfaces, change)) {
info.getChanges().remove(j);
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
info.getChanges().remove(j);
@@ -1515,14 +1535,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return false;
}
SurfaceControl openingLeash = null;
+ SurfaceControl closingLeash = null;
if (mApps != null) {
for (int i = mApps.length - 1; i >= 0; --i) {
if (mApps[i].mode == MODE_OPENING) {
openingLeash = mApps[i].leash;
+ } else if (mApps[i].mode == MODE_CLOSING) {
+ closingLeash = mApps[i].leash;
}
}
}
- if (openingLeash != null) {
+ if (openingLeash != null && closingLeash != null) {
int rootIdx = -1;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change c = info.getChanges().get(i);
@@ -1532,6 +1555,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
st.reparent(c.getLeash(), openingLeash);
st.setAlpha(c.getLeash(), 1.0f);
rootIdx = TransitionUtil.rootIndexFor(c, info);
+ } else if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && c.getMode() == TRANSIT_CHANGE) {
+ st.reparent(c.getLeash(), closingLeash);
}
}
// The root leash and the leash of opening target should actually in the same level,
@@ -1666,22 +1692,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return INVALID_TASK_ID;
}
- private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
- WindowContainerToken token, TransitionInfo.Change change) {
- final ComponentName openChange = findComponentName(change);
- final int firstTaskId = findTaskId(change);
- final WindowContainerToken openToken = findToken(change);
- return (openChange != null && openChange.equals(topActivity))
- || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
- || (openToken != null && openToken.equals(token));
- }
-
- static boolean isOpenChangeMatched(@NonNull ArrayList<OpenChangeInfo> targets,
+ static boolean isOpenSurfaceMatched(@NonNull ArrayList<SurfaceControl> openSurfaces,
TransitionInfo.Change change) {
- for (int i = targets.size() - 1; i >= 0; --i) {
- final OpenChangeInfo next = targets.get(i);
- if (isSameChangeTarget(next.mOpenComponent, next.mOpenTaskId, next.mOpenToken,
- change)) {
+ for (int i = openSurfaces.size() - 1; i >= 0; --i) {
+ if (openSurfaces.get(i).isSameSurface(change.getLeash())) {
return true;
}
}
@@ -1745,16 +1759,4 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
}
-
- static class OpenChangeInfo {
- final ComponentName mOpenComponent;
- final int mOpenTaskId;
- final WindowContainerToken mOpenToken;
- OpenChangeInfo(ComponentName openComponent, int openTaskId,
- WindowContainerToken openToken) {
- mOpenComponent = openComponent;
- mOpenTaskId = openTaskId;
- mOpenToken = openToken;
- }
- }
}
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 37e8ead4fc78..5f0eed9daa1a 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
@@ -833,7 +833,7 @@ public class BubbleController implements ConfigurationChangeListener,
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData, mLogger);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
@@ -1212,7 +1212,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
public void startBubbleDrag(String bubbleKey) {
if (mBubbleData.getSelectedBubble() != null) {
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+ collapseExpandedViewForBubbleBar();
}
if (mBubbleStateListener != null) {
boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
@@ -1304,6 +1304,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
mLayerView.showExpandedView(mBubbleData.getOverflow());
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
return;
}
@@ -1315,6 +1316,7 @@ public class BubbleController implements ConfigurationChangeListener,
// already in the stack
mBubbleData.setSelectedBubbleFromLauncher(b);
mLayerView.showExpandedView(b);
+ mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// TODO: (b/271468319) handle overflow
} else {
@@ -1759,6 +1761,9 @@ public class BubbleController implements ConfigurationChangeListener,
@MainThread
public void removeAllBubbles(@Bubbles.DismissReason int reason) {
mBubbleData.dismissAll(reason);
+ if (reason == Bubbles.DISMISS_USER_GESTURE) {
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR);
+ }
}
private void onEntryAdded(BubbleEntry entry) {
@@ -2021,12 +2026,16 @@ public class BubbleController implements ConfigurationChangeListener,
public void expansionChanged(boolean isExpanded) {
// in bubble bar mode, let the request to show the expanded view come from launcher.
// only collapse here if we're collapsing.
- if (mLayerView != null && !isExpanded) {
- if (mBubblePositioner.isImeVisible()) {
- // If we're collapsing, hide the IME
- hideCurrentInputMethod();
- }
- mLayerView.collapse();
+ if (!isExpanded) {
+ collapseExpandedViewForBubbleBar();
+ }
+
+ BubbleLogger.Event event = isExpanded ? BubbleLogger.Event.BUBBLE_BAR_EXPANDED
+ : BubbleLogger.Event.BUBBLE_BAR_COLLAPSED;
+ if (mBubbleData.getSelectedBubble() instanceof Bubble bubble) {
+ mLogger.log(bubble, event);
+ } else {
+ mLogger.log(event);
}
}
@@ -2179,6 +2188,16 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ private void collapseExpandedViewForBubbleBar() {
+ if (mLayerView != null && mLayerView.isExpanded()) {
+ if (mBubblePositioner.isImeVisible()) {
+ // If we're collapsing, hide the IME
+ hideCurrentInputMethod();
+ }
+ mLayerView.collapse();
+ }
+ }
+
private void updateOverflowButtonDot() {
BubbleOverflow overflow = mBubbleData.getOverflow();
if (overflow == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 6d757d26a9bd..36630733e1da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -145,6 +145,9 @@ public class BubbleLogger {
@UiEvent(doc = "bubble promoted from overflow back to bubble bar")
BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR(1949),
+ @UiEvent(doc = "while bubble bar is expanded, switch to another/existing bubble")
+ BUBBLE_BAR_BUBBLE_SWITCHED(1977)
+
// endregion
;
@@ -165,8 +168,14 @@ public class BubbleLogger {
}
/**
- * @param b Bubble involved in this UI event
- * @param e UI event
+ * Log an UIEvent
+ */
+ public void log(UiEventLogger.UiEventEnum e) {
+ mUiEventLogger.log(e);
+ }
+
+ /**
+ * Log an UIEvent with the given bubble info
*/
public void log(Bubble b, UiEventLogger.UiEventEnum e) {
mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 35a0d07a63b2..88f55b8af8f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1704,6 +1704,7 @@ public class BubbleStackView extends FrameLayout
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ stopMonitoringSwipeUpGesture();
}
@Override
@@ -2313,7 +2314,8 @@ public class BubbleStackView extends FrameLayout
/**
* Stop monitoring for swipe up gesture
*/
- void stopMonitoringSwipeUpGesture() {
+ @VisibleForTesting
+ public void stopMonitoringSwipeUpGesture() {
stopMonitoringSwipeUpGestureInternal();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
index 2612b81aae00..e577c3e0b1b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
@@ -141,4 +141,10 @@ public class AnimatableScaleMatrix extends Matrix {
// PhysicsAnimator's animator caching).
return obj == this;
}
+
+ @Override
+ public int hashCode() {
+ // Make sure equals and hashCode work in a similar way. Rely on object identity for both.
+ return System.identityHashCode(this);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 07463bb024a2..34259bfb7aaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
+import androidx.annotation.VisibleForTesting
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.shared.bubbles.DismissView
import com.android.wm.shell.shared.bubbles.RelativeTouchListener
@@ -32,7 +33,7 @@ class BubbleBarExpandedViewDragController(
private val animationHelper: BubbleBarAnimationHelper,
private val bubblePositioner: BubblePositioner,
private val pinController: BubbleExpandedViewPinController,
- private val dragListener: DragListener
+ @get:VisibleForTesting val dragListener: DragListener,
) {
var isStuckToDismiss: Boolean = false
@@ -107,7 +108,7 @@ class BubbleBarExpandedViewDragController(
viewInitialX: Float,
viewInitialY: Float,
dx: Float,
- dy: Float
+ dy: Float,
) {
if (!isMoving) {
isMoving = true
@@ -127,7 +128,7 @@ class BubbleBarExpandedViewDragController(
dx: Float,
dy: Float,
velX: Float,
- velY: Float
+ velY: Float,
) {
finishDrag()
}
@@ -152,7 +153,7 @@ class BubbleBarExpandedViewDragController(
private inner class MagnetListener : MagnetizedObject.MagnetListener {
override fun onStuckToTarget(
target: MagnetizedObject.MagneticTarget,
- draggedObject: MagnetizedObject<*>
+ draggedObject: MagnetizedObject<*>,
) {
isStuckToDismiss = true
pinController.onStuckToDismissTarget()
@@ -163,7 +164,7 @@ class BubbleBarExpandedViewDragController(
draggedObject: MagnetizedObject<*>,
velX: Float,
velY: Float,
- wasFlungOut: Boolean
+ wasFlungOut: Boolean,
) {
isStuckToDismiss = false
animationHelper.animateUnstuckFromDismissView(target)
@@ -171,7 +172,7 @@ class BubbleBarExpandedViewDragController(
override fun onReleasedInTarget(
target: MagnetizedObject.MagneticTarget,
- draggedObject: MagnetizedObject<*>
+ draggedObject: MagnetizedObject<*>,
) {
dragListener.onReleased(inDismiss = true)
pinController.onDragEnd()
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 1367b7e24bc7..402818c80b01 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
@@ -34,10 +34,12 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
+import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
@@ -69,6 +71,7 @@ public class BubbleBarLayerView extends FrameLayout
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
+ private final BubbleLogger mBubbleLogger;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
@@ -93,11 +96,13 @@ public class BubbleBarLayerView extends FrameLayout
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData,
+ BubbleLogger bubbleLogger) {
super(context);
mBubbleController = controller;
mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
+ mBubbleLogger = bubbleLogger;
mAnimationHelper = new BubbleBarAnimationHelper(context,
this, mPositioner);
@@ -233,6 +238,11 @@ 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);
+ }
}
};
mDragController = new BubbleBarExpandedViewDragController(
@@ -413,4 +423,10 @@ public class BubbleBarLayerView extends FrameLayout
}
}
+ @Nullable
+ @VisibleForTesting
+ public BubbleBarExpandedViewDragController getDragController() {
+ return mDragController;
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 85921703d559..813772f20a8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -71,6 +71,11 @@ public class DividerSnapAlgorithm {
*/
private static final int SNAP_MODE_MINIMIZED = 3;
+ /**
+ * A mode where apps can be "flexibly offscreen" on smaller displays.
+ */
+ private static final int SNAP_FLEXIBLE_SPLIT = 4;
+
private final float mMinFlingVelocityPxPerSecond;
private final float mMinDismissVelocityPxPerSecond;
private final int mDisplayWidth;
@@ -78,6 +83,7 @@ public class DividerSnapAlgorithm {
private final int mDividerSize;
private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
private final Rect mInsets = new Rect();
+ private final Rect mPinnedTaskbarInsets = new Rect();
private final int mSnapMode;
private final boolean mFreeSnapMode;
private final int mMinimalSizeResizableTask;
@@ -88,6 +94,8 @@ public class DividerSnapAlgorithm {
/** Allows split ratios that go offscreen (a.k.a. "flexible split") */
private final boolean mAllowOffscreenRatios;
private final boolean mIsLeftRightSplit;
+ /** In SNAP_MODE_MINIMIZED, the side of the screen on which an app will "dock" when minimized */
+ private final int mDockSide;
/** The first target which is still splitting the screen */
private final SnapTarget mFirstSplitTarget;
@@ -101,14 +109,14 @@ public class DividerSnapAlgorithm {
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isLeftRightSplit, Rect insets, int dockSide) {
+ boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide) {
this(res, displayWidth, displayHeight, dividerSize, isLeftRightSplit, insets,
- dockSide, false /* minimized */, true /* resizable */);
+ pinnedTaskbarInsets, dockSide, false /* minimized */, true /* resizable */);
}
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode,
- boolean isHomeResizable) {
+ boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide,
+ boolean isMinimizedMode, boolean isHomeResizable) {
mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond =
@@ -117,9 +125,18 @@ public class DividerSnapAlgorithm {
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
mIsLeftRightSplit = isLeftRightSplit;
+ mDockSide = dockSide;
mInsets.set(insets);
- mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
- res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ mPinnedTaskbarInsets.set(pinnedTaskbarInsets);
+ if (Flags.enableFlexibleTwoAppSplit()) {
+ mSnapMode = SNAP_FLEXIBLE_SPLIT;
+ } else {
+ // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config
+ mSnapMode = isMinimizedMode
+ ? SNAP_MODE_MINIMIZED
+ : res.getInteger(
+ com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ }
mFreeSnapMode = res.getBoolean(
com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
mFixedRatio = res.getFraction(
@@ -129,11 +146,10 @@ public class DividerSnapAlgorithm {
mCalculateRatiosBasedOnAvailableSpace = res.getBoolean(
com.android.internal.R.bool.config_flexibleSplitRatios);
// If this is a small screen or a foldable, use offscreen ratios
- mAllowOffscreenRatios =
- Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res);
+ mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res);
mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
- calculateTargets(isLeftRightSplit, dockSide);
+ calculateTargets();
mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
mDismissStartTarget = mTargets.get(0);
@@ -269,28 +285,31 @@ public class DividerSnapAlgorithm {
return mTargets.get(minIndex);
}
- private void calculateTargets(boolean isLeftRightSplit, int dockedSide) {
+ private void calculateTargets() {
mTargets.clear();
- int dividerMax = isLeftRightSplit
+ int dividerMax = mIsLeftRightSplit
? mDisplayWidth
: mDisplayHeight;
int startPos = -mDividerSize;
- if (dockedSide == DOCKED_RIGHT) {
+ if (mDockSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
- addRatio16_9Targets(isLeftRightSplit, dividerMax);
+ addRatio16_9Targets(mIsLeftRightSplit, dividerMax);
break;
case SNAP_FIXED_RATIO:
- addFixedDivisionTargets(isLeftRightSplit, dividerMax);
+ addFixedDivisionTargets(mIsLeftRightSplit, dividerMax);
break;
case SNAP_ONLY_1_1:
- addMiddleTarget(isLeftRightSplit);
+ addMiddleTarget(mIsLeftRightSplit);
break;
case SNAP_MODE_MINIMIZED:
- addMinimizedTarget(isLeftRightSplit, dockedSide);
+ addMinimizedTarget(mIsLeftRightSplit, mDockSide);
+ break;
+ case SNAP_FLEXIBLE_SPLIT:
+ addFlexSplitTargets(mIsLeftRightSplit, dividerMax);
break;
}
mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
@@ -299,9 +318,9 @@ public class DividerSnapAlgorithm {
private void addNonDismissingTargets(boolean isLeftRightSplit, int topPosition,
int bottomPosition, int dividerMax) {
@PersistentSnapPosition int firstTarget =
- mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66;
+ areOffscreenRatiosSupported() ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66;
@PersistentSnapPosition int lastTarget =
- mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33;
+ areOffscreenRatiosSupported() ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33;
maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget);
addMiddleTarget(isLeftRightSplit);
maybeAddTarget(bottomPosition,
@@ -313,19 +332,35 @@ public class DividerSnapAlgorithm {
int end = isLeftRightSplit
? mDisplayWidth - mInsets.right
: mDisplayHeight - mInsets.bottom;
- int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
- if (mAllowOffscreenRatios) {
- // TODO (b/349828130): This is a placeholder value, real measurements to come
- size = (int) (0.3f * (end - start)) - mDividerSize / 2;
- } else if (mCalculateRatiosBasedOnAvailableSpace) {
+ int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+ if (mCalculateRatiosBasedOnAvailableSpace) {
size = Math.max(size, mMinimalSizeResizableTask);
}
+
int topPosition = start + size;
int bottomPosition = end - size - mDividerSize;
addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax);
}
+ private void addFlexSplitTargets(boolean isLeftRightSplit, int dividerMax) {
+ int start = 0;
+ int end = isLeftRightSplit ? mDisplayWidth : mDisplayHeight;
+ int pinnedTaskbarShiftStart = isLeftRightSplit
+ ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top;
+ int pinnedTaskbarShiftEnd = isLeftRightSplit
+ ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom;
+
+ float ratio = areOffscreenRatiosSupported()
+ ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
+ : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+ int size = (int) (ratio * (end - start)) - mDividerSize / 2;
+
+ int leftTopPosition = start + pinnedTaskbarShiftStart + size;
+ int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+ addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
+ }
+
private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) {
int start = isLeftRightSplit ? mInsets.left : mInsets.top;
int end = isLeftRightSplit
@@ -347,7 +382,7 @@ public class DividerSnapAlgorithm {
* meets the minimal size requirement.
*/
private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) {
- if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) {
+ if (smallerSize >= mMinimalSizeResizableTask || areOffscreenRatiosSupported()) {
mTargets.add(new SnapTarget(position, snapPosition));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index e7848e27d7ed..cf858deb0327 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -480,6 +480,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mLastDraggingPosition,
position,
mSplitLayout.FLING_RESIZE_DURATION,
+ Interpolators.FAST_OUT_SLOW_IN,
() -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */));
mMoving = false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f73065ea8eb8..dab30b0f0b96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -28,6 +28,7 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
+import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
@@ -48,10 +49,13 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.view.Display;
+import android.view.InsetsController;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.RoundedCorner;
@@ -77,7 +81,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
@@ -100,6 +103,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_ENTER_DURATION = 450;
private static final int FLING_EXIT_DURATION = 450;
+ private static final int FLING_OFFSCREEN_DURATION = 500;
+
+ /** A split ratio used on larger screens, where we can fit both apps onscreen. */
+ public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f;
+ /** A split ratio used on smaller screens, where we place one app mostly offscreen. */
+ public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f;
// Here are some (arbitrarily decided) layer definitions used during animations to make sure the
// layers stay in order. Note: This does not affect any other layer numbering systems because
@@ -147,6 +156,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final ResizingEffectPolicy mSurfaceEffectPolicy;
private final ShellTaskOrganizer mTaskOrganizer;
private final InsetsState mInsetsState = new InsetsState();
+ private Insets mPinnedTaskbarInsets = Insets.NONE;
private Context mContext;
@VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
@@ -517,6 +527,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override
public void insetsChanged(InsetsState insetsState) {
mInsetsState.set(insetsState);
+
if (!mInitialized) {
return;
}
@@ -525,9 +536,51 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// flicker.
return;
}
+
+ // Check to see if insets changed in such a way that the divider algorithm needs to be
+ // recalculated.
+ Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
+ if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
+ mPinnedTaskbarInsets = pinnedTaskbarInsets;
+ // Refresh the DividerSnapAlgorithm.
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+ // If the divider is no longer placed on a snap point, animate it to the nearest one.
+ DividerSnapAlgorithm.SnapTarget snapTarget =
+ findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
+ if (snapTarget.position != mDividerPosition) {
+ snapToTarget(mDividerPosition, snapTarget,
+ InsetsController.ANIMATION_DURATION_RESIZE,
+ InsetsController.RESIZE_INTERPOLATOR);
+ }
+ }
+
mSplitWindowManager.onInsetsChanged(insetsState);
}
+ /**
+ * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only
+ * pinned Taskbar does this, and only when the IME is not showing.
+ */
+ private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
+ if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+ return Insets.NONE;
+ }
+
+ // If IME is not showing...
+ for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ // and Taskbar is pinned...
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
+ // Return Insets representing the pinned taskbar state.
+ return source.calculateVisibleInsets(mRootBounds);
+ }
+ }
+
+ // Else, divider can calculate based on the full display.
+ return Insets.NONE;
+ }
+
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
@@ -604,25 +657,35 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* Sets new divider position and updates bounds correspondingly. Notifies listener if the new
* target indicates dismissing split.
*/
- public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
+ public void snapToTarget(int currentPosition, SnapTarget snapTarget, int duration,
+ Interpolator interpolator) {
switch (snapTarget.snapPosition) {
case SNAP_TO_START_AND_DISMISS:
- flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
case SNAP_TO_END_AND_DISMISS:
- flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
default:
- flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
() -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
}
+ /**
+ * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation
+ * duration and interpolator.
+ */
+ public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
+ snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
+ FAST_OUT_SLOW_IN);
+ }
+
void onStartDragging() {
mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
CUJ_SPLIT_SCREEN_RESIZE);
@@ -667,6 +730,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerSize,
mIsLeftRightSplit,
insets,
+ mPinnedTaskbarInsets.toRect(),
mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
@@ -674,14 +738,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void flingDividerToDismiss(boolean toEnd, int reason) {
final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
: mDividerSnapAlgorithm.getDismissStartTarget().position;
- flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION,
+ flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION, FAST_OUT_SLOW_IN,
() -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
}
/** Fling divider from current position to center position. */
public void flingDividerToCenter(@Nullable Runnable finishCallback) {
final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
- flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION,
+ flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
() -> {
setDividerPosition(pos, true /* applyLayoutChange */);
if (finishCallback != null) {
@@ -699,14 +763,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) {
switch (currentSnapPosition) {
case SNAP_TO_2_10_90 ->
- snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget());
+ snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget(),
+ FLING_OFFSCREEN_DURATION, EMPHASIZED);
case SNAP_TO_2_90_10 ->
- snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget());
+ snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget(),
+ FLING_OFFSCREEN_DURATION, EMPHASIZED);
}
}
@VisibleForTesting
- void flingDividerPosition(int from, int to, int duration,
+ void flingDividerPosition(int from, int to, int duration, Interpolator interpolator,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
if (flingFinishedCallback != null) {
@@ -724,7 +790,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerFlingAnimator = ValueAnimator
.ofInt(from, to)
.setDuration(duration);
- mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mDividerFlingAnimator.setInterpolator(interpolator);
// If the divider is being physically controlled by the user, we use a cool parallax effect
// on the task windows. So if this "snap" animation is an extension of a user-controlled
@@ -1048,6 +1114,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return (int) (minWidth / density);
}
+ public int getDisplayWidth() {
+ return mRootBounds.width();
+ }
+
+ public int getDisplayHeight() {
+ return mRootBounds.height();
+ }
+
/**
* Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
* restore shifted configuration bounds if it's no longer shifted.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 65bf389f3819..9113c0a53178 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -142,13 +142,25 @@ public class SplitScreenUtils {
}
/**
+ * Convenience function for {@link #isLargeScreen(Configuration)}.
+ */
+ public static boolean isLargeScreen(Resources res) {
+ return isLargeScreen(res.getConfiguration());
+ }
+
+ /**
+ * Returns whether the current device is a foldable
+ */
+ public static boolean isFoldable(Resources res) {
+ return res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+ }
+
+ /**
* Returns whether we should allow split ratios to go offscreen or not. If the device is a phone
* or a foldable (either screen), we allow it.
*/
public static boolean allowOffscreenRatios(Resources res) {
- return !isLargeScreen(res.getConfiguration())
- ||
- res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+ return Flags.enableFlexibleTwoAppSplit() && (!isLargeScreen(res) || isFoldable(res));
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index 688f8ca2dc75..49c2785f1ecd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -68,7 +68,7 @@ class CompatUILayout extends LinearLayout {
private void setViewVisibility(@IdRes int resId, boolean show) {
final View view = findViewById(resId);
- int visibility = show ? View.VISIBLE : View.GONE;
+ int visibility = show ? View.VISIBLE : View.INVISIBLE;
if (view.getVisibility() == visibility) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
index b141bebbe8b1..fd1bbc477cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -100,7 +100,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
private void setViewVisibility(@IdRes int resId, boolean show) {
final View view = findViewById(resId);
- int visibility = show ? View.VISIBLE : View.GONE;
+ int visibility = show ? View.VISIBLE : View.INVISIBLE;
if (view.getVisibility() == visibility) {
return;
}
@@ -171,7 +171,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
+ view.setVisibility(View.INVISIBLE);
}
});
fadeOut.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
new file mode 100644
index 000000000000..752d2fd721a5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
@@ -0,0 +1,2 @@
+# WM Shell sub-module dagger owners
+jorgegil@google.com \ No newline at end of file
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 0942e058bbdc..817be3b1fe0d 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.dagger;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
+import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
import android.annotation.Nullable;
import android.app.KeyguardManager;
@@ -87,6 +88,8 @@ import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
@@ -153,12 +156,7 @@ import java.util.Optional;
* <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(
- includes = {
- WMShellBaseModule.class,
- PipModule.class,
- ShellBackAnimationModule.class
- })
+@Module(includes = {WMShellBaseModule.class, PipModule.class, ShellBackAnimationModule.class})
public abstract class WMShellModule {
//
@@ -173,8 +171,7 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static BubblePositioner provideBubblePositioner(Context context,
- WindowManager windowManager) {
+ static BubblePositioner provideBubblePositioner(Context context, WindowManager windowManager) {
return new BubblePositioner(context, windowManager);
}
@@ -186,20 +183,22 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static BubbleData provideBubbleData(Context context,
+ static BubbleData provideBubbleData(
+ Context context,
BubbleLogger logger,
BubblePositioner positioner,
BubbleEducationController educationController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellBackgroundThread ShellExecutor bgExecutor) {
- return new BubbleData(context, logger, positioner, educationController, mainExecutor,
- bgExecutor);
+ return new BubbleData(
+ context, logger, positioner, educationController, mainExecutor, bgExecutor);
}
// Note: Handler needed for LauncherApps.register
@WMSingleton
@Provides
- static BubbleController provideBubbleController(Context context,
+ static BubbleController provideBubbleController(
+ Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -224,14 +223,38 @@ public abstract class WMShellModule {
Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
- return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
- null /* synchronizer */, floatingContentCoordinator,
- new BubbleDataRepository(launcherApps, mainExecutor, bgExecutor,
+ return new BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ data,
+ null /* synchronizer */,
+ floatingContentCoordinator,
+ new BubbleDataRepository(
+ launcherApps,
+ mainExecutor,
+ bgExecutor,
new BubblePersistentRepository(context)),
- statusBarService, windowManager, windowManagerShellWrapper, userManager,
- launcherApps, logger, taskStackListener, organizer, positioner, displayController,
- oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, transitions, syncQueue, wmService,
+ statusBarService,
+ windowManager,
+ windowManagerShellWrapper,
+ userManager,
+ launcherApps,
+ logger,
+ taskStackListener,
+ organizer,
+ positioner,
+ displayController,
+ oneHandedOptional,
+ dragAndDropController,
+ mainExecutor,
+ mainHandler,
+ bgExecutor,
+ taskViewTransitions,
+ transitions,
+ syncQueue,
+ wmService,
ProdBubbleProperties.INSTANCE);
}
@@ -318,9 +341,7 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static AppToWebGenericLinksParser provideGenericLinksParser(
- Context context,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
+ Context context, @ShellMainThread ShellExecutor mainExecutor) {
return new AppToWebGenericLinksParser(context, mainExecutor);
}
@@ -328,8 +349,7 @@ public abstract class WMShellModule {
static AssistContentRequester provideAssistContentRequester(
Context context,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread ShellExecutor bgExecutor
- ) {
+ @ShellBackgroundThread ShellExecutor bgExecutor) {
return new AssistContentRequester(context, shellExecutor, bgExecutor);
}
@@ -366,15 +386,20 @@ public abstract class WMShellModule {
Optional<DesktopRepository> desktopRepository,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
- WindowDecorViewModel windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel,
+ Optional<TaskChangeListener> taskChangeListener) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
- ShellInit init = FreeformComponents.isFreeformEnabled(context)
- ? shellInit
- : null;
- return new FreeformTaskListener(context, init, shellTaskOrganizer,
- desktopRepository, desktopTasksController, launchAdjacentController,
- windowDecorViewModel);
+ ShellInit init = FreeformComponents.isFreeformEnabled(context) ? shellInit : null;
+ return new FreeformTaskListener(
+ context,
+ init,
+ shellTaskOrganizer,
+ desktopRepository,
+ desktopTasksController,
+ launchAdjacentController,
+ windowDecorViewModel,
+ taskChangeListener);
}
@WMSingleton
@@ -385,10 +410,7 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor) {
return new FreeformTaskTransitionHandler(
- transitions,
- displayController,
- mainExecutor,
- animExecutor);
+ transitions, displayController, mainExecutor, animExecutor);
}
@WMSingleton
@@ -402,8 +424,13 @@ public abstract class WMShellModule {
Optional<TaskChangeListener> taskChangeListener,
FocusTransitionObserver focusTransitionObserver) {
return new FreeformTaskTransitionObserver(
- context, shellInit, transitions, desktopImmersiveController,
- windowDecorViewModel, taskChangeListener, focusTransitionObserver);
+ context,
+ shellInit,
+ transitions,
+ desktopImmersiveController,
+ windowDecorViewModel,
+ taskChangeListener,
+ focusTransitionObserver);
}
@WMSingleton
@@ -419,8 +446,8 @@ public abstract class WMShellModule {
} else {
transitionStarter = freeformTaskTransitionHandler;
}
- return new FreeformTaskTransitionStarterInitializer(shellInit, windowDecorViewModel,
- transitionStarter);
+ return new FreeformTaskTransitionStarterInitializer(
+ shellInit, windowDecorViewModel, transitionStarter);
}
//
@@ -431,7 +458,8 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static OneHandedController provideOneHandedController(Context context,
+ static OneHandedController provideOneHandedController(
+ Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -443,9 +471,19 @@ public abstract class WMShellModule {
InteractionJankMonitor jankMonitor,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
- windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
- uiEventLogger, mainExecutor, mainHandler);
+ return OneHandedController.create(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ windowManager,
+ displayController,
+ displayLayout,
+ taskStackListener,
+ jankMonitor,
+ uiEventLogger,
+ mainExecutor,
+ mainHandler);
}
//
@@ -477,12 +515,29 @@ public abstract class WMShellModule {
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
- return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
- shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
- displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, launchAdjacentController,
- windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
- multiInstanceHelper, mainExecutor, mainHandler);
+ return new SplitScreenController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
+ displayController,
+ displayImeController,
+ displayInsetsController,
+ dragAndDropController,
+ transitions,
+ transactionPool,
+ iconProvider,
+ recentTasks,
+ launchAdjacentController,
+ windowDecorViewModel,
+ desktopTasksController,
+ null /* stageCoordinator */,
+ multiInstanceHelper,
+ mainExecutor,
+ mainHandler);
}
//
@@ -502,10 +557,16 @@ public abstract class WMShellModule {
Optional<UnfoldTransitionHandler> unfoldHandler,
Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
- return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTransitionController, recentsTransitionHandler,
- keyguardTransitionHandler, desktopTasksController,
- unfoldHandler, activityEmbeddingController);
+ return new DefaultMixedHandler(
+ shellInit,
+ transitions,
+ splitScreenOptional,
+ pipTransitionController,
+ recentsTransitionHandler,
+ keyguardTransitionHandler,
+ desktopTasksController,
+ unfoldHandler,
+ activityEmbeddingController);
}
@WMSingleton
@@ -516,8 +577,12 @@ public abstract class WMShellModule {
Transitions transitions,
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
- return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
- recentTasksController.orElse(null), homeTransitionObserver);
+ return new RecentsTransitionHandler(
+ shellInit,
+ shellTaskOrganizer,
+ transitions,
+ recentTasksController.orElse(null),
+ homeTransitionObserver);
}
//
@@ -534,8 +599,7 @@ public abstract class WMShellModule {
FullscreenUnfoldTaskAnimator fullscreenAnimator,
Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
ShellInit shellInit,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
+ @ShellMainThread ShellExecutor mainExecutor) {
final List<UnfoldTaskAnimator> animators = new ArrayList<>();
animators.add(splitAnimator);
animators.add(fullscreenAnimator);
@@ -546,8 +610,7 @@ public abstract class WMShellModule {
progressProvider.get(),
animators,
unfoldTransitionHandler,
- mainExecutor
- );
+ mainExecutor);
}
@Provides
@@ -555,10 +618,9 @@ public abstract class WMShellModule {
Context context,
UnfoldBackgroundController unfoldBackgroundController,
ShellController shellController,
- DisplayInsetsController displayInsetsController
- ) {
- return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController,
- shellController, displayInsetsController);
+ DisplayInsetsController displayInsetsController) {
+ return new FullscreenUnfoldTaskAnimator(
+ context, unfoldBackgroundController, shellController, displayInsetsController);
}
@Provides
@@ -568,14 +630,18 @@ public abstract class WMShellModule {
ShellController shellController,
@ShellMainThread ShellExecutor executor,
Lazy<Optional<SplitScreenController>> splitScreenOptional,
- DisplayInsetsController displayInsetsController
- ) {
+ DisplayInsetsController displayInsetsController) {
// TODO(b/238217847): The lazy reference here causes some dependency issues since it
// immediately registers a listener on that controller on init. We should reference the
// controller directly once we refactor ShellTaskOrganizer to not depend on the unfold
// animation controller directly.
- return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
- shellController, backgroundController, displayInsetsController);
+ return new SplitTaskUnfoldAnimator(
+ context,
+ executor,
+ splitScreenOptional,
+ shellController,
+ backgroundController,
+ displayInsetsController);
}
@WMSingleton
@@ -602,8 +668,15 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor executor,
@ShellMainThread Handler handler,
ShellInit shellInit) {
- return new UnfoldTransitionHandler(shellInit, progressProvider.get(), animator,
- unfoldAnimator, transactionPool, executor, handler, transitions);
+ return new UnfoldTransitionHandler(
+ shellInit,
+ progressProvider.get(),
+ animator,
+ unfoldAnimator,
+ transactionPool,
+ executor,
+ handler,
+ transitions);
}
@WMSingleton
@@ -632,6 +705,7 @@ public abstract class WMShellModule {
Transitions transitions,
KeyguardManager keyguardManager,
ReturnToDragStartAnimator returnToDragStartAnimator,
+ Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
@@ -652,18 +726,39 @@ public abstract class WMShellModule {
FocusTransitionObserver focusTransitionObserver,
DesktopModeEventLogger desktopModeEventLogger,
DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
- return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
- displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- dragAndDropController, transitions, keyguardManager,
- returnToDragStartAnimator, enterDesktopTransitionHandler,
- exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
+ return new DesktopTasksController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ displayController,
+ shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
+ dragAndDropController,
+ transitions,
+ keyguardManager,
+ returnToDragStartAnimator,
+ desktopMixedTransitionHandler.get(),
+ enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler,
+ desktopModeDragAndDropTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopImmersiveController.get(),
+ dragToDesktopTransitionHandler,
+ desktopImmersiveController.get(),
desktopRepository,
- desktopModeLoggerTransitionObserver, launchAdjacentController,
- recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
- recentTasksController.orElse(null), interactionJankMonitor, mainHandler,
- inputManager, focusTransitionObserver, desktopModeEventLogger,
+ desktopModeLoggerTransitionObserver,
+ launchAdjacentController,
+ recentsTransitionHandler,
+ multiInstanceHelper,
+ mainExecutor,
+ desktopTasksLimiter,
+ recentTasksController.orElse(null),
+ interactionJankMonitor,
+ mainHandler,
+ inputManager,
+ focusTransitionObserver,
+ desktopModeEventLogger,
desktopTilingDecorViewModel);
}
@@ -693,10 +788,11 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static Optional<TaskChangeListener> provideDesktopTaskChangeListener(Context context) {
- if (Flags.enableWindowingTransitionHandlersObservers() &&
- DesktopModeStatus.canEnterDesktopMode(context)) {
- return Optional.of(new DesktopTaskChangeListener());
+ static Optional<TaskChangeListener> provideDesktopTaskChangeListener(
+ Context context, @DynamicOverride DesktopRepository desktopRepository) {
+ if (ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue()
+ && DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.of(new DesktopTaskChangeListener(desktopRepository));
}
return Optional.empty();
}
@@ -724,8 +820,7 @@ public abstract class WMShellModule {
maxTaskLimit,
interactionJankMonitor,
context,
- handler)
- );
+ handler));
}
@WMSingleton
@@ -739,10 +834,7 @@ public abstract class WMShellModule {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopImmersiveController(
- transitions,
- desktopRepository,
- displayController,
- shellTaskOrganizer));
+ transitions, desktopRepository, displayController, shellTaskOrganizer));
}
return Optional.empty();
}
@@ -750,11 +842,10 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static ReturnToDragStartAnimator provideReturnToDragStartAnimator(
- Context context, InteractionJankMonitor interactionJankMonitor) {
- return new ReturnToDragStartAnimator(context, interactionJankMonitor);
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ReturnToDragStartAnimator(interactionJankMonitor);
}
-
@WMSingleton
@Provides
static DragToDesktopTransitionHandler provideDragToDesktopTransitionHandler(
@@ -762,12 +853,12 @@ public abstract class WMShellModule {
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor) {
- return (Flags.enableDesktopWindowingTransitions() ||
- ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
- ? new SpringDragToDesktopTransitionHandler(context, transitions,
- rootTaskDisplayAreaOrganizer, interactionJankMonitor)
- : new DefaultDragToDesktopTransitionHandler(context, transitions,
- rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+ return (Flags.enableDesktopWindowingTransitions()
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
+ ? new SpringDragToDesktopTransitionHandler(
+ context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+ : new DefaultDragToDesktopTransitionHandler(
+ context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
}
@WMSingleton
@@ -802,30 +893,29 @@ public abstract class WMShellModule {
static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
Context context,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor
- ) {
+ @ShellAnimationThread ShellExecutor animExecutor) {
return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
}
@WMSingleton
@Provides
static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
- Transitions transitions
- ) {
+ Transitions transitions) {
return new DesktopModeDragAndDropTransitionHandler(transitions);
}
@WMSingleton
@Provides
@DynamicOverride
-
static DesktopRepository provideDesktopRepository(
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
+ DesktopRepositoryInitializer desktopRepositoryInitializer,
@ShellMainThread CoroutineScope mainScope
) {
return new DesktopRepository(context, shellInit, desktopPersistentRepository,
+ desktopRepositoryInitializer,
mainScope);
}
@@ -837,12 +927,16 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopRepository desktopRepository
- ) {
+ @DynamicOverride DesktopRepository desktopRepository) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
- return Optional.of(new DesktopActivityOrientationChangeHandler(
- context, shellInit, shellTaskOrganizer, taskStackListener,
- toggleResizeDesktopTaskTransitionHandler, desktopRepository));
+ return Optional.of(
+ new DesktopActivityOrientationChangeHandler(
+ context,
+ shellInit,
+ shellTaskOrganizer,
+ taskStackListener,
+ toggleResizeDesktopTaskTransitionHandler,
+ desktopRepository));
}
return Optional.empty();
}
@@ -854,12 +948,16 @@ public abstract class WMShellModule {
Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
- ShellInit shellInit
- ) {
- return desktopRepository.flatMap(repository ->
- Optional.of(new DesktopTasksTransitionObserver(
- context, repository, transitions, shellTaskOrganizer, shellInit))
- );
+ ShellInit shellInit) {
+ return desktopRepository.flatMap(
+ repository ->
+ Optional.of(
+ new DesktopTasksTransitionObserver(
+ context,
+ repository,
+ transitions,
+ shellTaskOrganizer,
+ shellInit)));
}
@WMSingleton
@@ -870,8 +968,11 @@ public abstract class WMShellModule {
@DynamicOverride DesktopRepository desktopRepository,
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
+ Optional<DesktopImmersiveController> desktopImmersiveController,
InteractionJankMonitor interactionJankMonitor,
- @ShellMainThread Handler handler
+ @ShellMainThread Handler handler,
+ ShellInit shellInit,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
@@ -883,8 +984,11 @@ public abstract class WMShellModule {
desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
+ desktopImmersiveController.get(),
interactionJankMonitor,
- handler));
+ handler,
+ shellInit,
+ rootTaskDisplayAreaOrganizer));
}
@WMSingleton
@@ -929,12 +1033,11 @@ public abstract class WMShellModule {
@Provides
static DesktopWindowingEducationTooltipController
provideDesktopWindowingEducationTooltipController(
- Context context,
- AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
- DisplayController displayController
- ) {
- return new DesktopWindowingEducationTooltipController(context,
- additionalSystemViewContainerFactory, displayController);
+ Context context,
+ AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+ DisplayController displayController) {
+ return new DesktopWindowingEducationTooltipController(
+ context, additionalSystemViewContainerFactory, displayController);
}
@OptIn(markerClass = ExperimentalCoroutinesApi.class)
@@ -946,22 +1049,35 @@ public abstract class WMShellModule {
AppHandleEducationDatastoreRepository appHandleEducationDatastoreRepository,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopWindowingEducationTooltipController desktopWindowingEducationTooltipController,
- @ShellMainThread CoroutineScope applicationScope, @ShellBackgroundThread
- MainCoroutineDispatcher backgroundDispatcher) {
- return new AppHandleEducationController(context, appHandleEducationFilter,
- appHandleEducationDatastoreRepository, windowDecorCaptionHandleRepository,
- desktopWindowingEducationTooltipController, applicationScope,
+ @ShellMainThread CoroutineScope applicationScope,
+ @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher) {
+ return new AppHandleEducationController(
+ context,
+ appHandleEducationFilter,
+ appHandleEducationDatastoreRepository,
+ windowDecorCaptionHandleRepository,
+ desktopWindowingEducationTooltipController,
+ applicationScope,
backgroundDispatcher);
}
@WMSingleton
@Provides
static DesktopPersistentRepository provideDesktopPersistentRepository(
- Context context,
- @ShellBackgroundThread CoroutineScope bgScope) {
+ Context context, @ShellBackgroundThread CoroutineScope bgScope) {
return new DesktopPersistentRepository(context, bgScope);
}
+ @WMSingleton
+ @Provides
+ static DesktopRepositoryInitializer provideDesktopRepositoryInitializer(
+ Context context,
+ DesktopPersistentRepository desktopPersistentRepository,
+ @ShellMainThread CoroutineScope mainScope) {
+ return new DesktopRepositoryInitializerImpl(context, desktopPersistentRepository,
+ mainScope);
+ }
+
//
// Drag and drop
//
@@ -969,14 +1085,14 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static GlobalDragListener provideGlobalDragListener(
- IWindowManager wmService,
- @ShellMainThread ShellExecutor mainExecutor) {
+ IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) {
return new GlobalDragListener(wmService, mainExecutor);
}
@WMSingleton
@Provides
- static DragAndDropController provideDragAndDropController(Context context,
+ static DragAndDropController provideDragAndDropController(
+ Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
@@ -987,9 +1103,18 @@ public abstract class WMShellModule {
GlobalDragListener globalDragListener,
Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- shellTaskOrganizer, displayController, uiEventLogger, iconProvider,
- globalDragListener, transitions, mainExecutor);
+ return new DragAndDropController(
+ context,
+ shellInit,
+ shellController,
+ shellCommandHandler,
+ shellTaskOrganizer,
+ displayController,
+ uiEventLogger,
+ iconProvider,
+ globalDragListener,
+ transitions,
+ mainExecutor);
}
//
@@ -1003,8 +1128,7 @@ public abstract class WMShellModule {
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional
- ) {
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
return new Object();
}
}
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 d0bc5f0955f7..f69aa6df6a1d 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
@@ -130,7 +130,8 @@ class DesktopImmersiveController(
displayId: Int
) {
if (!Flags.enableFullyImmersiveInDesktop()) return
- exitImmersiveIfApplicable(wct, displayId)?.invoke(transition)
+ val result = exitImmersiveIfApplicable(wct, displayId)
+ result.asExit()?.runOnTransitionStart?.invoke(transition)
}
/**
@@ -145,16 +146,23 @@ class DesktopImmersiveController(
wct: WindowContainerTransaction,
displayId: Int,
excludeTaskId: Int? = null,
- ): ((IBinder) -> Unit)? {
- if (!Flags.enableFullyImmersiveInDesktop()) return null
- val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null
+ ): ExitResult {
+ if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
+ val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId)
+ ?: return ExitResult.NoExit
if (immersiveTask == excludeTaskId) {
- return null
+ return ExitResult.NoExit
}
- val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null
+ val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask)
+ ?: return ExitResult.NoExit
logV("Appending immersive exit for task: $immersiveTask in display: $displayId")
wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
- return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) }
+ return ExitResult.Exit(
+ exitingTask = immersiveTask,
+ runOnTransitionStart = { transition ->
+ addPendingImmersiveExit(immersiveTask, displayId, transition)
+ }
+ )
}
/**
@@ -167,22 +175,25 @@ class DesktopImmersiveController(
fun exitImmersiveIfApplicable(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
- ): ((IBinder) -> Unit)? {
- if (!Flags.enableFullyImmersiveInDesktop()) return null
+ ): ExitResult {
+ if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
// A full immersive task is being minimized, make sure the immersive state is broken
// (i.e. resize back to max bounds).
wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
logV("Appending immersive exit for task: ${taskInfo.taskId}")
- return { transition ->
- addPendingImmersiveExit(
- taskId = taskInfo.taskId,
- displayId = taskInfo.displayId,
- transition = transition
- )
- }
+ return ExitResult.Exit(
+ exitingTask = taskInfo.taskId,
+ runOnTransitionStart = { transition ->
+ addPendingImmersiveExit(
+ taskId = taskInfo.taskId,
+ displayId = taskInfo.displayId,
+ transition = transition
+ )
+ }
+ )
}
- return null
+ return ExitResult.NoExit
}
@@ -213,9 +224,9 @@ class DesktopImmersiveController(
finishTransaction: SurfaceControl.Transaction,
finishCallback: Transitions.TransitionFinishCallback
): Boolean {
- logD("startAnimation transition=%s", transition)
val state = requireState()
if (transition != state.transition) return false
+ logD("startAnimation transition=%s", transition)
animateResize(
targetTaskId = state.taskId,
info = info,
@@ -323,7 +334,6 @@ class DesktopImmersiveController(
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
) {
- logD("onTransitionReady transition=%s", transition)
// Check if this is a pending external exit transition.
val pendingExit = pendingExternalExitTransitions
.firstOrNull { pendingExit -> pendingExit.transition == transition }
@@ -391,7 +401,6 @@ class DesktopImmersiveController(
}
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
- logD("onTransitionMerged merged=%s playing=%s", merged, playing)
val pendingExit = pendingExternalExitTransitions
.firstOrNull { pendingExit -> pendingExit.transition == merged }
if (pendingExit != null) {
@@ -404,7 +413,6 @@ class DesktopImmersiveController(
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- logD("onTransitionFinished transition=%s aborted=%b", transition, aborted)
val pendingExit = pendingExternalExitTransitions
.firstOrNull { pendingExit -> pendingExit.transition == transition }
if (pendingExit != null) {
@@ -461,6 +469,20 @@ class DesktopImmersiveController(
var transition: IBinder,
)
+ /** The result of an external exit request. */
+ sealed class ExitResult {
+ /** An immersive task exit (meaning, resize) was appended to the request. */
+ data class Exit(
+ val exitingTask: Int,
+ val runOnTransitionStart: ((IBinder) -> Unit)
+ ) : ExitResult()
+ /** There was no exit appended to the request. */
+ data object NoExit : ExitResult()
+
+ /** Returns the result as an [Exit] or null if it isn't of that type. */
+ fun asExit(): Exit? = if (this is Exit) this else null
+ }
+
private enum class Direction {
ENTER, EXIT
}
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 54a07f20fd84..48bb2a8b4a74 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
@@ -25,17 +25,24 @@ import android.view.SurfaceControl
import android.view.WindowManager
import android.window.DesktopModeFlags
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
+import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.MixedTransitionHandler
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
/** The [Transitions.TransitionHandler] coordinates transition handlers in desktop windowing. */
class DesktopMixedTransitionHandler(
@@ -44,10 +51,20 @@ class DesktopMixedTransitionHandler(
private val desktopRepository: DesktopRepository,
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
+ private val desktopImmersiveController: DesktopImmersiveController,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
+ shellInit: ShellInit,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
) : MixedTransitionHandler, FreeformTaskTransitionStarter {
+ init {
+ shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
+ }
+
+ @VisibleForTesting
+ val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
+
/** Delegates starting transition to [FreeformTaskTransitionHandler]. */
override fun startWindowingModeTransition(
targetWindowingMode: Int,
@@ -65,6 +82,48 @@ class DesktopMixedTransitionHandler(
}
requireNotNull(wct)
return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+ .also { transition ->
+ pendingMixedTransitions.add(PendingMixedTransition.Close(transition))
+ }
+ }
+
+ /**
+ * Starts a launch transition for [taskId], with an optional [exitingImmersiveTask] if it was
+ * included in the [wct] and is expected to be animated by this handler.
+ */
+ fun startLaunchTransition(
+ @WindowManager.TransitionType transitionType: Int,
+ wct: WindowContainerTransaction,
+ taskId: Int,
+ minimizingTaskId: Int? = null,
+ exitingImmersiveTask: Int? = null,
+ ): IBinder {
+ if (!Flags.enableFullyImmersiveInDesktop() &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ return transitions.startTransition(transitionType, wct, /* handler= */ null)
+ }
+ if (exitingImmersiveTask == null) {
+ logV("Starting mixed launch transition for task#%d", taskId)
+ } else {
+ logV(
+ "Starting mixed launch transition for task#%d with immersive exit of task#%d",
+ taskId, exitingImmersiveTask
+ )
+ }
+ return transitions.startTransition(transitionType, wct, /* handler= */ this)
+ .also { transition ->
+ pendingMixedTransitions.add(PendingMixedTransition.Launch(
+ transition = transition,
+ launchingTask = taskId,
+ minimizingTask = minimizingTaskId,
+ exitingImmersiveTask = exitingImmersiveTask,
+ ))
+ }
+ }
+
+ /** Notifies this handler that there is a pending transition for it to handle. */
+ fun addPendingMixedTransition(pendingMixedTransition: PendingMixedTransition) {
+ pendingMixedTransitions.add(pendingMixedTransition)
}
/** Returns null, as it only handles transitions started from Shell. */
@@ -78,11 +137,43 @@ class DesktopMixedTransitionHandler(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback,
+ finishCallback: TransitionFinishCallback,
+ ): Boolean {
+ val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
+ ?: return false.also {
+ logV("No pending desktop transition")
+ }
+ pendingMixedTransitions.remove(pending)
+ logV("Animating pending mixed transition: %s", pending)
+ return when (pending) {
+ is PendingMixedTransition.Close -> animateCloseTransition(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
+ is PendingMixedTransition.Launch -> animateLaunchTransition(
+ pending,
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
+ }
+ }
+
+ private fun animateCloseTransition(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback,
): Boolean {
val closeChange = findCloseDesktopTaskChange(info)
if (closeChange == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: Should have closing desktop task", TAG)
+ logW("Should have closing desktop task")
return false
}
if (isLastDesktopTask(closeChange)) {
@@ -106,6 +197,85 @@ class DesktopMixedTransitionHandler(
)
}
+ private fun animateLaunchTransition(
+ pending: PendingMixedTransition.Launch,
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback,
+ ): Boolean {
+ // Check if there's also an immersive change during this launch.
+ val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
+ findDesktopTaskChange(info, exitingTask)
+ }
+ val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
+ findDesktopTaskChange(info, minimizingTask)
+ }
+ val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+ ?: error("Should have pending launching task change")
+
+ var subAnimationCount = -1
+ var combinedWct: WindowContainerTransaction? = null
+ val finishCb = TransitionFinishCallback { wct ->
+ --subAnimationCount
+ combinedWct = combinedWct.merge(wct)
+ if (subAnimationCount > 0) return@TransitionFinishCallback
+ finishCallback.onTransitionFinished(combinedWct)
+ }
+
+ logV(
+ "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
+ launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
+ immersiveExitChange?.taskInfo?.taskId
+ )
+ if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ // Only apply minimize change reparenting here if we implement the new app launch
+ // transitions, otherwise this reparenting is handled in the default handler.
+ minimizeChange?.let {
+ applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
+ }
+ }
+ if (immersiveExitChange != null) {
+ subAnimationCount = 2
+ // Animate the immersive exit change separately.
+ info.changes.remove(immersiveExitChange)
+ desktopImmersiveController.animateResizeChange(
+ immersiveExitChange,
+ startTransaction,
+ finishTransaction,
+ finishCb
+ )
+ // Let the leftover/default handler animate the remaining changes.
+ return dispatchToLeftoverHandler(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCb
+ )
+ }
+ // There's nothing to animate separately, so let the left over handler animate
+ // the entire transition.
+ subAnimationCount = 1
+ return dispatchToLeftoverHandler(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCb
+ )
+ }
+
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishTransaction: SurfaceControl.Transaction?
+ ) {
+ pendingMixedTransitions.removeAll { pending -> pending.transition == transition }
+ super.onTransitionConsumed(transition, aborted, finishTransaction)
+ }
+
/**
* Dispatch close desktop task animation to the default transition handlers. Allows delegating
* it to Launcher to animate in sync with show Home transition.
@@ -116,7 +286,7 @@ class DesktopMixedTransitionHandler(
change: TransitionInfo.Change,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback,
+ finishCallback: TransitionFinishCallback,
): Boolean {
// Starting the jank trace if closing the last window in desktop mode.
interactionJankMonitor.begin(
@@ -126,14 +296,56 @@ class DesktopMixedTransitionHandler(
CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
)
// Dispatch the last desktop task closing animation.
+ return dispatchToLeftoverHandler(
+ transition = transition,
+ info = info,
+ startTransaction = startTransaction,
+ finishTransaction = finishTransaction,
+ finishCallback = finishCallback,
+ doOnFinishCallback = {
+ // Finish the jank trace when closing the last window in desktop mode.
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
+ }
+ )
+ }
+
+ /**
+ * Reparent the minimizing task back to its root display area.
+ *
+ * During the launch/minimize animation the all animated tasks will be reparented to a
+ * transition leash shown in front of other desktop tasks. Reparenting the minimizing task back
+ * to its root display area ensures that task stays behind other desktop tasks during the
+ * animation.
+ */
+ private fun applyMinimizeChangeReparenting(
+ info: TransitionInfo,
+ minimizeChange: Change,
+ startTransaction: SurfaceControl.Transaction,
+ ) {
+ require(TransitionUtil.isOpeningMode(info.type))
+ require(minimizeChange.taskInfo != null)
+ val taskInfo = minimizeChange.taskInfo!!
+ require(taskInfo.isFreeform)
+ logV("Reparenting minimizing task#%d", taskInfo.taskId)
+ rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
+ taskInfo.displayId, minimizeChange.leash, startTransaction)
+ }
+
+ private fun dispatchToLeftoverHandler(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback,
+ doOnFinishCallback: (() -> Unit)? = null,
+ ): Boolean {
return transitions.dispatchTransition(
transition,
info,
startTransaction,
finishTransaction,
{ wct ->
- // Finish the jank trace when closing the last window in desktop mode.
- interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
+ doOnFinishCallback?.invoke()
finishCallback.onTransitionFinished(wct)
},
/* skip= */ this
@@ -155,6 +367,44 @@ class DesktopMixedTransitionHandler(
}
}
+ private fun findDesktopTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
+ return info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
+ }
+
+ private fun WindowContainerTransaction?.merge(
+ wct: WindowContainerTransaction?
+ ): WindowContainerTransaction? {
+ if (wct == null) return this
+ if (this == null) return wct
+ return this.merge(wct)
+ }
+
+ /** A scheduled transition that will potentially be animated by more than one handler */
+ sealed class PendingMixedTransition {
+ abstract val transition: IBinder
+
+ /** A task is closing. */
+ data class Close(
+ override val transition: IBinder,
+ ) : PendingMixedTransition()
+
+ /** A task is opening or moving to front. */
+ data class Launch(
+ override val transition: IBinder,
+ val launchingTask: Int,
+ val minimizingTask: Int?,
+ val exitingImmersiveTask: Int?,
+ ) : PendingMixedTransition()
+ }
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
companion object {
private const val TAG = "DesktopMixedTransitionHandler"
}
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 255ca6e2511f..bed484c7a532 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
@@ -541,7 +541,8 @@ class DesktopModeEventLogger {
FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__RETURN_HOME_OR_OVERVIEW
),
TASK_FINISHED(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED),
- SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF)
+ SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF),
+ TASK_MINIMIZED(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_MINIMIZED),
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index ed03982d191d..41febdfb3c2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -382,6 +382,7 @@ class DesktopModeLoggerTransitionObserver(
transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT ->
ExitReason.KEYBOARD_SHORTCUT_EXIT
transitionInfo.isExitToRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+ transitionInfo.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED
else -> {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 61de0777ed05..09e77fee7527 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
@@ -259,6 +260,7 @@ public class DesktopModeVisualIndicator {
FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Desktop Mode Visual Indicator");
lp.setTrustedOverlay();
+ lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
null /* hostInputToken */);
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 6f7b7162d2ec..fda709a4a2d7 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
@@ -23,14 +23,14 @@ import android.util.ArrayMap
import android.util.ArraySet
import android.util.SparseArray
import android.view.Display.INVALID_DISPLAY
+import android.window.DesktopModeFlags
import android.window.WindowContainerToken
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -46,6 +46,7 @@ class DesktopRepository (
private val context: Context,
shellInit: ShellInit,
private val persistentRepository: DesktopPersistentRepository,
+ private val repositoryInitializer: DesktopRepositoryInitializer,
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
){
@@ -120,35 +121,7 @@ class DesktopRepository (
}
private fun initRepoFromPersistentStorage() {
- if (!Flags.enableDesktopWindowingPersistence()) return
- // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
- mainCoroutineScope.launch {
- val desktop = persistentRepository.readDesktop() ?: return@launch
-
- val maxTasks =
- DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
- ?: desktop.zOrderedTasksCount
-
- var visibleTasksCount = 0
- desktop.zOrderedTasksList
- // Reverse it so we initialize the repo from bottom to top.
- .reversed()
- .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
- .forEach { task ->
- if (task.desktopTaskState == DesktopTaskState.VISIBLE
- && visibleTasksCount < maxTasks
- ) {
- visibleTasksCount++
- addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
- addActiveTask(desktop.displayId, task.taskId)
- updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
- } else {
- addActiveTask(desktop.displayId, task.taskId)
- updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
- minimizeTask(desktop.displayId, task.taskId)
- }
- }
- }
+ repositoryInitializer.initialize(this)
}
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
@@ -204,8 +177,15 @@ class DesktopRepository (
visibleTasksListeners.remove(visibleTasksListener)
}
+ /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */
+ fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
+ addOrMoveFreeformTaskToTop(displayId, taskId)
+ addActiveTask(displayId, taskId)
+ updateTask(displayId, taskId, isVisible)
+ }
+
/** Adds task with [taskId] to the list of active tasks on [displayId]. */
- fun addActiveTask(displayId: Int, taskId: Int) {
+ private fun addActiveTask(displayId: Int, taskId: Int) {
// Removes task if it is active on another display excluding [displayId].
removeActiveTask(taskId, excludedDisplayId = displayId)
@@ -219,7 +199,7 @@ class DesktopRepository (
fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
if ((displayId != excludedDisplayId)
- && desktopTaskData.activeTasks.remove(taskId)) {
+ && desktopTaskData.activeTasks.remove(taskId)) {
logD("Removed active task=%d displayId=%d", taskId, displayId)
updateActiveTasksListeners(displayId)
}
@@ -292,8 +272,8 @@ class DesktopRepository (
* If task was visible on a different display with a different [displayId], removes from
* the set of visible tasks on that display and notifies listeners.
*/
- fun updateTaskVisibility(displayId: Int, taskId: Int, visible: Boolean) {
- if (visible) {
+ fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
+ if (isVisible) {
// If task is visible, remove it from any other display besides [displayId].
removeVisibleTask(taskId, excludedDisplayId = displayId)
} else if (displayId == INVALID_DISPLAY) {
@@ -302,7 +282,7 @@ class DesktopRepository (
return
}
val prevCount = getVisibleTaskCount(displayId)
- if (visible) {
+ if (isVisible) {
desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
@@ -311,9 +291,12 @@ class DesktopRepository (
val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
logD("Update task visibility taskId=%d visible=%b displayId=%d",
- taskId, visible, displayId)
+ taskId, isVisible, displayId)
logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
notifyVisibleTaskListeners(displayId, newCount)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
+ updatePersistentRepository(displayId)
+ }
}
}
@@ -355,13 +338,13 @@ class DesktopRepository (
*
* Unminimizes the task if it is minimized.
*/
- fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
+ private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
- if (Flags.enableDesktopWindowingPersistence()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
updatePersistentRepository(displayId)
}
}
@@ -378,8 +361,8 @@ class DesktopRepository (
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
}
-
- if (Flags.enableDesktopWindowingPersistence()) {
+ updateTask(displayId, taskId, isVisible = false)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
updatePersistentRepository(displayId)
}
}
@@ -426,8 +409,8 @@ class DesktopRepository (
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
removeActiveTask(taskId)
- updateTaskVisibility(displayId, taskId, visible = false)
- if (Flags.enableDesktopWindowingPersistence()) {
+ removeVisibleTask(taskId)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
updatePersistentRepository(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
index 1ee2de958e55..94ac2e665f61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -17,21 +17,56 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.window.DesktopModeFlags
import com.android.wm.shell.freeform.TaskChangeListener
/** Manages tasks handling specific to Android Desktop Mode. */
-class DesktopTaskChangeListener: TaskChangeListener {
+class DesktopTaskChangeListener(
+ private val desktopRepository: DesktopRepository,
+) : TaskChangeListener {
override fun onTaskOpening(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Connect this with DesktopRepository.
+ if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ return
+ }
+ if (isFreeformTask(taskInfo)) {
+ desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ }
}
override fun onTaskChanging(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Connect this with DesktopRepository.
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+
+ // Case 1: Freeform task is changed in Desktop Mode.
+ if (isFreeformTask(taskInfo)) {
+ if (taskInfo.isVisible) {
+ desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ }
+ desktopRepository.updateTask(
+ taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ } else {
+ // Case 2: Freeform task is changed outside Desktop Mode.
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ }
+ }
+
+ // This method should only be used for scenarios where the task info changes are not propagated to
+ // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver].
+ // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
+ // of race conditions and possible duplications with [onTaskChanging].
+ override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method.
}
override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Connect this with DesktopRepository.
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ if (!isFreeformTask(taskInfo)) {
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ }
+ // TODO: b/367268953 - Connect this with DesktopRepository for handling
+ // task moving to front for tasks in windowing mode.
}
override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
@@ -39,6 +74,20 @@ class DesktopTaskChangeListener: TaskChangeListener {
}
override fun onTaskClosing(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Connect this with DesktopRepository.
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
+ desktopRepository.isClosingTask(taskInfo.taskId)) {
+ // A task that's vanishing should be removed:
+ // - If it's closed by the X button which means it's marked as a closing task.
+ desktopRepository.removeClosingTask(taskInfo.taskId)
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ } else {
+ desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false)
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ }
}
+
+ private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
}
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 eec2ba5f41cd..75f8839692a2 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
@@ -54,6 +54,7 @@ import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.widget.Toast
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
@@ -87,6 +88,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
+import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
@@ -146,6 +148,7 @@ class DesktopTasksController(
private val transitions: Transitions,
private val keyguardManager: KeyguardManager,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
+ private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
@@ -379,7 +382,7 @@ class DesktopTasksController(
// TODO(342378842): Instead of using default display, support multiple displays
val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
DEFAULT_DISPLAY, wct, taskId)
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
+ val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
displayId = DEFAULT_DISPLAY,
excludeTaskId = taskId,
@@ -393,7 +396,7 @@ class DesktopTasksController(
// TODO(343149901): Add DPI changes for task launch
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
- runOnTransit?.invoke(transition)
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
@@ -410,7 +413,7 @@ class DesktopTasksController(
}
logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
exitSplitIfApplicable(wct, task)
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
+ val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
displayId = task.displayId,
excludeTaskId = task.taskId,
@@ -422,7 +425,7 @@ class DesktopTasksController(
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
- runOnTransit?.invoke(transition)
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
/**
@@ -459,12 +462,12 @@ class DesktopTasksController(
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
+ val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
wct, taskInfo.displayId)
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
transition?.let {
taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
- runOnTransit?.invoke(transition)
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
}
@@ -507,7 +510,8 @@ class DesktopTasksController(
taskId
)
)
- return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)
+ return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo).asExit()
+ ?.runOnTransitionStart
}
fun minimizeTask(taskInfo: RunningTaskInfo) {
@@ -520,7 +524,7 @@ class DesktopTasksController(
removeWallpaperActivity(wct)
}
// Notify immersive handler as it might need to exit immersive state.
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)
+ val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)
wct.reorder(taskInfo.token, false)
val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
@@ -531,7 +535,7 @@ class DesktopTasksController(
taskId = taskId
)
}
- runOnTransit?.invoke(transition)
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -627,7 +631,7 @@ class DesktopTasksController(
logV("moveBackgroundTaskToFront taskId=%s", taskId)
val wct = WindowContainerTransaction()
// TODO: b/342378842 - Instead of using default display, support multiple displays
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
+ val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
displayId = DEFAULT_DISPLAY,
excludeTaskId = taskId,
@@ -638,8 +642,13 @@ class DesktopTasksController(
launchWindowingMode = WINDOWING_MODE_FREEFORM
}.toBundle(),
)
- val transition = startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition)
- runOnTransit?.invoke(transition)
+ val transition = startLaunchTransition(
+ TRANSIT_OPEN,
+ wct,
+ taskId,
+ remoteTransition = remoteTransition
+ )
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
/**
@@ -658,32 +667,40 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
+ val result = desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
displayId = taskInfo.displayId,
excludeTaskId = taskInfo.taskId,
)
- val transition =
- startLaunchTransition(
- TRANSIT_TO_FRONT,
- wct,
- taskInfo.taskId,
- remoteTransition,
- taskInfo.displayId
- )
- runOnTransit?.invoke(transition)
+ val exitResult = if (result is ExitResult.Exit) { result } else { null }
+ val transition = startLaunchTransition(
+ transitionType = TRANSIT_TO_FRONT,
+ wct = wct,
+ taskId = taskInfo.taskId,
+ exitingImmersiveTask = exitResult?.exitingTask,
+ remoteTransition = remoteTransition,
+ displayId = taskInfo.displayId,
+ )
+ exitResult?.runOnTransitionStart?.invoke(transition)
}
private fun startLaunchTransition(
transitionType: Int,
wct: WindowContainerTransaction,
taskId: Int,
- remoteTransition: RemoteTransition?,
+ exitingImmersiveTask: Int? = null,
+ remoteTransition: RemoteTransition? = null,
displayId: Int = DEFAULT_DISPLAY,
): IBinder {
val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
if (remoteTransition == null) {
- val t = transitions.startTransition(transitionType, wct, null /* handler */)
+ val t = desktopMixedTransitionHandler.startLaunchTransition(
+ transitionType = transitionType,
+ wct = wct,
+ taskId = taskId,
+ minimizingTaskId = taskIdToMinimize,
+ exitingImmersiveTask = exitingImmersiveTask,
+ )
taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
return t
}
@@ -698,7 +715,7 @@ class DesktopTasksController(
mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
- taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
+ taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
return t
}
@@ -842,6 +859,38 @@ class DesktopTasksController(
toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
}
+ private fun dragToMaximizeDesktopTask(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ currentDragBounds: Rect,
+ motionEvent: MotionEvent
+ ) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ if (isTaskMaximized(taskInfo, stableBounds)) {
+ // Handle the case where we attempt to drag-to-maximize when already maximized: the task
+ // position won't need to change but we want to animate the surface going back to the
+ // maximized position.
+ val containerBounds = taskInfo.configuration.windowConfiguration.bounds
+ if (containerBounds != currentDragBounds) {
+ returnToDragStartAnimator.start(
+ taskInfo.taskId,
+ taskSurface,
+ startBounds = currentDragBounds,
+ endBounds = containerBounds,
+ )
+ }
+ return
+ }
+
+ // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top.
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
+ )
+ toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent)
+ }
+
private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
if (taskInfo.isResizeable) {
// if resizable then expand to entire stable bounds (full display minus insets)
@@ -964,7 +1013,6 @@ class DesktopTasksController(
taskSurface,
startBounds = currentDragBounds,
endBounds = destinationBounds,
- isResizable = taskInfo.isResizeable,
)
}
return
@@ -998,7 +1046,13 @@ class DesktopTasksController(
taskSurface,
startBounds = currentDragBounds,
endBounds = dragStartBounds,
- isResizable = taskInfo.isResizeable,
+ doOnEnd = {
+ Toast.makeText(
+ context,
+ com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
+ Toast.LENGTH_SHORT
+ ).show()
+ },
)
} else {
val resizeTrigger = if (position == SnapPosition.LEFT) {
@@ -1108,7 +1162,7 @@ class DesktopTasksController(
if (runningTaskInfo != null) {
// Task is already running, reorder it to the front
wct.reorder(runningTaskInfo.token, /* onTop= */ true)
- } else if (Flags.enableDesktopWindowingPersistence()) {
+ } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
// Task is not running, start it
wct.startTask(
taskId,
@@ -1250,10 +1304,7 @@ 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,
- request.type
- )
+ TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
// Check if the top task shouldn't be allowed to enter desktop mode
isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -1376,14 +1427,16 @@ class DesktopTasksController(
wct.startTask(requestedTaskId, options.toBundle())
val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
callingTask.displayId, wct, requestedTaskId)
- val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = callingTask.displayId,
- excludeTaskId = requestedTaskId,
- )
+ val exitResult = desktopImmersiveController
+ .exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = callingTask.displayId,
+ excludeTaskId = requestedTaskId,
+ )
val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
- runOnTransit?.invoke(transition)
+ addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
} else {
val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
splitScreenController.startTask(requestedTaskId, splitPosition,
@@ -1523,6 +1576,7 @@ class DesktopTasksController(
desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
// 2) minimize a Task if needed.
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+ addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
if (taskIdToMinimize != null) {
addPendingMinimizeTransition(transition, taskIdToMinimize)
return wct
@@ -1554,6 +1608,7 @@ class DesktopTasksController(
// minimize another Task.
val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+ addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
desktopImmersiveController.exitImmersiveIfApplicable(
transition, wct, task.displayId
)
@@ -1579,10 +1634,7 @@ class DesktopTasksController(
}
/** Handle task closing by removing wallpaper activity if it's the last active task */
- private fun handleTaskClosing(
- task: RunningTaskInfo,
- transitionType: Int
- ): WindowContainerTransaction? {
+ private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
logV("handleTaskClosing")
if (!isDesktopModeShowing(task.displayId))
return null
@@ -1603,7 +1655,7 @@ class DesktopTasksController(
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
- task.id
+ task.taskId
)
)
return if (wct.isEmpty) null else wct
@@ -1737,6 +1789,20 @@ class DesktopTasksController(
}
}
+ private fun addPendingAppLaunchTransition(
+ transition: IBinder,
+ launchTaskId: Int,
+ minimizeTaskId: Int?,
+ ) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ return
+ }
+ // TODO b/359523924: pass immersive task here?
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
+ transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
+ }
+
fun removeDesktop(displayId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
@@ -1908,11 +1974,15 @@ class DesktopTasksController(
)
when (indicatorType) {
IndicatorType.TO_FULLSCREEN_INDICATOR -> {
- moveToFullscreenWithAnimation(
- taskInfo,
- position,
- DesktopModeTransitionSource.TASK_DRAG
- )
+ if (DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)) {
+ dragToMaximizeDesktopTask(taskInfo, taskSurface, currentDragBounds, motionEvent)
+ } else {
+ moveToFullscreenWithAnimation(
+ taskInfo,
+ position,
+ DesktopModeTransitionSource.TASK_DRAG
+ )
+ }
}
IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
handleSnapResizingTask(
@@ -1937,17 +2007,32 @@ class DesktopTasksController(
)
}
IndicatorType.NO_INDICATOR -> {
+ // Create a copy so that we can animate from the current bounds if we end up having
+ // to snap the surface back without a WCT change.
+ val destinationBounds = Rect(currentDragBounds)
// If task bounds are outside valid drag area, snap them inward
DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
- currentDragBounds,
+ destinationBounds,
validDragArea
)
- if (currentDragBounds == dragStartBounds) return
+ if (destinationBounds == dragStartBounds) {
+ // There's no actual difference between the start and end bounds, so while a
+ // WCT change isn't needed, the dragged surface still needs to be snapped back
+ // to its original location.
+ releaseVisualIndicator()
+ returnToDragStartAnimator.start(
+ taskInfo.taskId,
+ taskSurface,
+ startBounds = currentDragBounds,
+ endBounds = dragStartBounds,
+ )
+ return
+ }
// Update task bounds so that the task position will match the position of its leash
val wct = WindowContainerTransaction()
- wct.setBounds(taskInfo.token, currentDragBounds)
+ wct.setBounds(taskInfo.token, destinationBounds)
transitions.startTransition(TRANSIT_CHANGE, wct, null)
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index dedd44f3950a..d537da802efd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -60,7 +60,8 @@ import java.util.function.Supplier;
* entering and exiting freeform.
*/
public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
- private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+ @VisibleForTesting
+ static final int FULLSCREEN_ANIMATION_DURATION = 336;
private final Context mContext;
private final Transitions mTransitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index f4df42cde10f..4e08d106052a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -19,28 +19,24 @@ package com.android.wm.shell.desktopmode
import android.animation.Animator
import android.animation.RectEvaluator
import android.animation.ValueAnimator
-import android.content.Context
import android.graphics.Rect
import android.view.SurfaceControl
-import android.widget.Toast
import androidx.core.animation.addListener
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
-import com.android.wm.shell.R
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
import java.util.function.Supplier
/** Animates the task surface moving from its current drag position to its pre-drag position. */
class ReturnToDragStartAnimator(
- private val context: Context,
private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
private val interactionJankMonitor: InteractionJankMonitor
) {
private var boundsAnimator: Animator? = null
private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
- constructor(context: Context, interactionJankMonitor: InteractionJankMonitor) :
- this(context, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+ constructor(interactionJankMonitor: InteractionJankMonitor) :
+ this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/** Sets a listener for the start and end of the reposition animation. */
fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -53,7 +49,7 @@ class ReturnToDragStartAnimator(
taskSurface: SurfaceControl,
startBounds: Rect,
endBounds: Rect,
- isResizable: Boolean
+ doOnEnd: (() -> Unit)? = null,
) {
val tx = transactionSupplier.get()
@@ -87,13 +83,7 @@ class ReturnToDragStartAnimator(
.apply()
taskRepositionAnimationListener.onAnimationEnd(taskId)
boundsAnimator = null
- if (!isResizable) {
- Toast.makeText(
- context,
- R.string.desktop_mode_non_resizable_snap_text,
- Toast.LENGTH_SHORT
- ).show()
- }
+ doOnEnd?.invoke()
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
}
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 2d11e02bd3c6..9e646f430c98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -50,7 +50,9 @@ class DesktopPersistentRepository(
DataStoreFactory.create(
serializer = DesktopPersistentRepositoriesSerializer,
produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
- scope = bgCoroutineScope))
+ scope = bgCoroutineScope,
+ ),
+ )
/** Provides `dataStore.data` flow and handles exceptions thrown during collection */
private val dataStoreFlow: Flow<DesktopPersistentRepositories> =
@@ -116,7 +118,11 @@ class DesktopPersistentRepository(
val desktop =
getDesktop(currentRepository, desktopId)
.toBuilder()
- .updateTaskStates(visibleTasks, minimizedTasks)
+ .updateTaskStates(
+ visibleTasks,
+ minimizedTasks,
+ freeformTasksInZOrder,
+ )
.updateZOrder(freeformTasksInZOrder)
desktopPersistentRepositories
@@ -169,9 +175,21 @@ class DesktopPersistentRepository(
private fun Desktop.Builder.updateTaskStates(
visibleTasks: ArraySet<Int>,
- minimizedTasks: ArraySet<Int>
+ minimizedTasks: ArraySet<Int>,
+ freeformTasksInZOrder: ArrayList<Int>,
): Desktop.Builder {
clearTasksByTaskId()
+
+ // Handle the case where tasks are not marked as visible but are meant to be visible
+ // after reboot. E.g. User moves out of desktop when there are multiple tasks are
+ // visible, they will be marked as not visible afterwards. This ensures that they are
+ // still persisted as visible.
+ // TODO - b/350476823: Remove this logic once repository holds expanded tasks
+ if (freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
+ visibleTasks.isEmpty()
+ ) {
+ visibleTasks.addAll(freeformTasksInZOrder.filterNot { it in minimizedTasks })
+ }
putAllTasksByTaskId(
visibleTasks.associateWith {
createDesktopTask(it, state = DesktopTaskState.VISIBLE)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
new file mode 100644
index 000000000000..771c3d1cb9a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.persistence
+
+import com.android.wm.shell.desktopmode.DesktopRepository
+
+/** Interface for initializing the [DesktopRepository]. */
+fun interface DesktopRepositoryInitializer {
+ fun initialize(repository: DesktopRepository)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
new file mode 100644
index 000000000000..d8156561ff19
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.persistence
+
+import android.content.Context
+import android.window.DesktopModeFlags
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Initializes the [DesktopRepository] from the [DesktopPersistentRepository].
+ *
+ * This class is responsible for reading the [DesktopPersistentRepository] and initializing the
+ * [DesktopRepository] with the tasks that previously existed in desktop.
+ */
+class DesktopRepositoryInitializerImpl(
+ private val context: Context,
+ private val persistentRepository: DesktopPersistentRepository,
+ @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+) : DesktopRepositoryInitializer {
+ override fun initialize(repository: DesktopRepository) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
+ // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
+ mainCoroutineScope.launch {
+ val desktop = persistentRepository.readDesktop() ?: return@launch
+
+ val maxTasks =
+ DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+ ?: desktop.zOrderedTasksCount
+
+ var visibleTasksCount = 0
+ desktop.zOrderedTasksList
+ // Reverse it so we initialize the repo from bottom to top.
+ .reversed()
+ .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
+ .forEach { task ->
+ if (task.desktopTaskState == DesktopTaskState.VISIBLE
+ && visibleTasksCount < maxTasks
+ ) {
+ visibleTasksCount++
+ repository.addTask(desktop.displayId, task.taskId, isVisible = false)
+ } else {
+ repository.addTask(desktop.displayId, task.taskId, isVisible = false)
+ repository.minimizeTask(desktop.displayId, task.taskId)
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index a16446fffa15..cd20d97c7964 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -34,7 +34,6 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
@@ -54,14 +53,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private final Optional<DesktopTasksController> mDesktopTasksController;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
+ private final Optional<TaskChangeListener> mTaskChangeListener;
private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State {
- RunningTaskInfo mTaskInfo;
- SurfaceControl mLeash;
- }
-
public FreeformTaskListener(
Context context,
ShellInit shellInit,
@@ -69,13 +64,15 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
Optional<DesktopRepository> desktopRepository,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
- WindowDecorViewModel windowDecorationViewModel) {
+ WindowDecorViewModel windowDecorationViewModel,
+ Optional<TaskChangeListener> taskChangeListener) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
mDesktopRepository = desktopRepository;
mDesktopTasksController = desktopTasksController;
mLaunchAdjacentController = launchAdjacentController;
+ mTaskChangeListener = taskChangeListener;
if (shellInit != null) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -100,14 +97,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
state.mLeash = leash;
mTasks.put(taskInfo.taskId, state);
- if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
+ DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopRepository.ifPresent(repository -> {
- repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
- if (taskInfo.isVisible) {
- repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
- repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId,
- /* visible= */ true);
- }
+ repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
});
}
updateLaunchAdjacentController();
@@ -119,7 +112,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
+ DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopRepository.ifPresent(repository -> {
// TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
@@ -129,7 +123,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
repository.removeClosingTask(taskInfo.taskId);
repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
} else {
- repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, false);
+ repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */
+ false);
repository.minimizeTask(taskInfo.displayId, taskInfo.taskId);
}
});
@@ -148,13 +143,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopRepository.ifPresent(repository -> {
- if (taskInfo.isVisible) {
- repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
- }
- repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId,
- taskInfo.isVisible);
- });
+ if (DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue()) {
+ // Pass task info changes to the [TaskChangeListener] since [TransitionsObserver]
+ // does not propagate all task info changes.
+ mTaskChangeListener.ifPresent(listener ->
+ listener.onNonTransitionTaskChanging(taskInfo));
+ } else {
+ mDesktopRepository.ifPresent(repository -> {
+ repository.updateTask(taskInfo.displayId, taskInfo.taskId,
+ taskInfo.isVisible);
+ });
+ }
}
updateLaunchAdjacentController();
}
@@ -179,7 +178,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId, taskInfo.isFocused);
if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
mDesktopRepository.ifPresent(repository -> {
- repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
+ repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
});
}
}
@@ -213,4 +212,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
public String toString() {
return TAG;
}
+
+ private static class State {
+ RunningTaskInfo mTaskInfo;
+ SurfaceControl mLeash;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 58337ece0991..e848b889b314 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -27,6 +27,7 @@ import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -38,6 +39,7 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.animation.MinimizeAnimator;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
@@ -137,7 +139,7 @@ public class FreeformTaskTransitionHandler
break;
case WindowManager.TRANSIT_TO_BACK:
transitionHandled |= startMinimizeTransition(
- transition, info.getType(), change);
+ transition, info.getType(), change, finishT, animations, onAnimFinish);
break;
case WindowManager.TRANSIT_CLOSE:
if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
@@ -206,7 +208,10 @@ public class FreeformTaskTransitionHandler
private boolean startMinimizeTransition(
IBinder transition,
int type,
- TransitionInfo.Change change) {
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction finishT,
+ ArrayList<Animator> animations,
+ Runnable onAnimFinish) {
if (!mPendingTransitionTokens.contains(transition)) {
return false;
}
@@ -215,7 +220,23 @@ public class FreeformTaskTransitionHandler
if (type != Transitions.TRANSIT_MINIMIZE) {
return false;
}
- // TODO(b/361524575): Add minimize animations
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ SurfaceControl sc = change.getLeash();
+ finishT.hide(sc);
+ final DisplayMetrics displayMetrics =
+ mDisplayController
+ .getDisplayContext(taskInfo.displayId).getResources().getDisplayMetrics();
+ final Animator animator = MinimizeAnimator.create(
+ displayMetrics,
+ change,
+ t,
+ (anim) -> {
+ animations.remove(anim);
+ onAnimFinish.run();
+ return null;
+ });
+ animations.add(animator);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 7631ece761b5..18f9cc758e38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -42,9 +42,9 @@ import java.util.Map;
import java.util.Optional;
/**
- * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes,
- * maximizing and restoring transitions. It also reports transitions so that window decorations can
- * be a part of transitions.
+ * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes, maximizing
+ * and restoring transitions. It also reports transitions so that window decorations can be a part
+ * of transitions.
*/
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
private final Transitions mTransitions;
@@ -89,8 +89,8 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
// TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository
// is updated from there **before** the |mWindowDecorViewModel| methods are invoked.
// Otherwise window decoration relayout won't run with the immersive state up to date.
- mDesktopImmersiveController.ifPresent(h ->
- h.onTransitionReady(transition, info, startT, finishT));
+ mDesktopImmersiveController.ifPresent(
+ h -> h.onTransitionReady(transition, info, startT, finishT));
}
// Update focus state first to ensure the correct state can be queried from listeners.
// TODO(371503964): Remove this once the unified task repository is ready.
@@ -147,33 +147,28 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mTaskChangeListener.ifPresent(
- listener -> listener.onTaskOpening(change.getTaskInfo()));
+ mTaskChangeListener.ifPresent(listener -> listener.onTaskOpening(change.getTaskInfo()));
mWindowDecorViewModel.onTaskOpening(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onCloseTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mTaskChangeListener.ifPresent(
- listener -> listener.onTaskClosing(change.getTaskInfo()));
+ mTaskChangeListener.ifPresent(listener -> listener.onTaskClosing(change.getTaskInfo()));
mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
-
}
private void onChangeTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mTaskChangeListener.ifPresent(listener ->
- listener.onTaskChanging(change.getTaskInfo()));
+ mTaskChangeListener.ifPresent(listener -> listener.onTaskChanging(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
-
private void onToFrontTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt
index 98bdf059e738..c0613e2dba48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt
@@ -28,7 +28,7 @@ import com.android.wm.shell.windowdecor.WindowDecorViewModel
class FreeformTaskTransitionStarterInitializer(
shellInit: ShellInit,
private val windowDecorViewModel: WindowDecorViewModel,
- private val freeformTaskTransitionStarter: FreeformTaskTransitionStarter
+ private val freeformTaskTransitionStarter: FreeformTaskTransitionStarter,
) {
init {
shellInit.addInitCallback(::onShellInit, this)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt
index f07c069bb420..aee92eaaf3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.freeform
-import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo
/**
* Interface used by [FreeformTaskTransitionObserver] to manage freeform tasks.
@@ -27,9 +27,18 @@ interface TaskChangeListener {
/** Notifies a task opening in freeform mode. */
fun onTaskOpening(taskInfo: RunningTaskInfo)
- /** Notifies a task info update on the given task. */
+ /** Notifies a task info update on the given task from Shell Transitions framework. */
fun onTaskChanging(taskInfo: RunningTaskInfo)
+ /**
+ * Notifies a task info update on the given task from [FreeformTaskListener].
+ *
+ * This is used to propagate task info changes since not all task changes are propagated from
+ * [TransitionObserver] in [onTaskChanging]. It is recommended to use [onTaskChanging] instead
+ * of this method where possible.
+ */
+ fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo)
+
/** Notifies a task moving to the front. */
fun onTaskMovingToFront(taskInfo: RunningTaskInfo)
@@ -38,4 +47,4 @@ interface TaskChangeListener {
/** Notifies a task is closing. */
fun onTaskClosing(taskInfo: RunningTaskInfo)
-} \ No newline at end of file
+}
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 f060158766fe..4aeecbec7dfb 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
@@ -30,6 +30,7 @@ import android.annotation.NonNull;
import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.Surface;
@@ -152,7 +153,6 @@ public class PipAnimationController {
return mCurrentAnimator;
}
- @SuppressWarnings("unchecked")
/**
* Construct and return an animator that animates from the {@param startBounds} to the
* {@param endBounds} with the given {@param direction}. If {@param direction} is type
@@ -171,6 +171,7 @@ public class PipAnimationController {
* leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
* rotation change.
*/
+ @SuppressWarnings("unchecked")
@VisibleForTesting
public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@@ -566,7 +567,7 @@ public class PipAnimationController {
}
getSurfaceTransactionHelper()
.resetScale(tx, leash, getDestinationBounds())
- .crop(tx, leash, getDestinationBounds())
+ .cropAndPosition(tx, leash, getDestinationBounds())
.round(tx, leash, true /* applyCornerRadius */)
.shadow(tx, leash, shouldApplyShadowRadius());
tx.show(leash);
@@ -590,18 +591,50 @@ public class PipAnimationController {
// 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 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) {
- initialSourceValue = new Rect(endValue);
+ // TODO(b/356277166): handle rotation change with activity that provides main window
+ // frame.
+ if (hasNonMatchFrame && !changeOrientation) {
+ endBounds.set(mainWindowFrame);
+ }
+ initialSourceValue = new Rect(endBounds);
+ } else if (isInPipDirection) {
+ if (hasNonMatchFrame) {
+ baseBounds.set(mainWindowFrame);
+ if (startValue.equals(baseValue)) {
+ // If the start value is at initial state as in PIP animation, also override
+ // the start bounds with nonMatchParentBounds.
+ startBounds.set(mainWindowFrame);
+ }
+ }
+ initialSourceValue = new Rect(baseBounds);
} else {
- initialSourceValue = new Rect(baseValue);
+ // Note that we assume the window bounds always match task bounds in PIP mode.
+ initialSourceValue = new Rect(baseBounds);
+ }
+
+ final Point leashOffset;
+ if (isInPipDirection) {
+ leashOffset = new Point(baseValue.left, baseValue.top);
+ } else if (isOutPipDirection) {
+ leashOffset = new Point(endValue.left, endValue.top);
+ } else {
+ leashOffset = new Point(baseValue.left, baseValue.top);
}
final Rect rotatedEndRect;
final Rect lastEndRect;
final Rect initialContainerRect;
- if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
- lastEndRect = new Rect(endValue);
- rotatedEndRect = new Rect(endValue);
+ 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);
@@ -617,9 +650,9 @@ public class PipAnimationController {
// Crop a Rect matches the aspect ratio and pivots at the center point.
// This is done for entering case only.
if (isInPipDirection(direction)) {
- final float aspectRatio = endValue.width() / (float) endValue.height();
+ final float aspectRatio = endBounds.width() / (float) endBounds.height();
adjustedSourceRectHint.set(PipUtils.getEnterPipWithOverlaySrcRectHint(
- startValue, aspectRatio));
+ startBounds, aspectRatio));
}
} else {
adjustedSourceRectHint.set(sourceRectHint);
@@ -644,7 +677,7 @@ public class PipAnimationController {
// construct new Rect instances in case they are recycled
return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
- endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
+ endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@@ -668,11 +701,22 @@ public class PipAnimationController {
setCurrentValue(bounds);
if (inScaleTransition() || adjustedSourceRectHint.isEmpty()) {
if (isOutPipDirection) {
- getSurfaceTransactionHelper().crop(tx, leash, end)
- .scale(tx, leash, end, bounds);
+ // Use the bounds relative to the task leash in case the leash does not
+ // start from (0, 0).
+ final Rect relativeEndBounds = new Rect(end);
+ relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
+ getSurfaceTransactionHelper()
+ .crop(tx, leash, relativeEndBounds)
+ .scale(tx, leash, relativeEndBounds, bounds,
+ false /* shouldOffset */);
} else {
- getSurfaceTransactionHelper().crop(tx, leash, base)
- .scale(tx, leash, base, bounds, angle)
+ // TODO(b/356277166): add support to specify sourceRectHint with
+ // non-match parent activity.
+ // If there's a PIP resize animation, we should offset the bounds to
+ // (0, 0) since the window bounds should match the leash bounds in PIP
+ // mode.
+ getSurfaceTransactionHelper().cropAndPosition(tx, leash, base)
+ .scale(tx, leash, base, bounds, angle, inScaleTransition())
.round(tx, leash, base, bounds)
.shadow(tx, leash, shouldApplyShadowRadius());
}
@@ -680,7 +724,7 @@ public class PipAnimationController {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
adjustedSourceRectHint, initialSourceValue, bounds, insets,
- isInPipDirection, fraction);
+ isInPipDirection, fraction, leashOffset);
final Rect sourceBounds = new Rect(initialContainerRect);
sourceBounds.inset(insets);
getSurfaceTransactionHelper()
@@ -733,8 +777,7 @@ public class PipAnimationController {
getSurfaceTransactionHelper()
.rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
insets, degree, x, y, isOutPipDirection,
- rotationDelta == ROTATION_270 /* clockwise */);
- getSurfaceTransactionHelper()
+ rotationDelta == ROTATION_270 /* clockwise */)
.round(tx, leash, sourceBounds, bounds)
.shadow(tx, leash, shouldApplyShadowRadius());
if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
@@ -772,7 +815,7 @@ public class PipAnimationController {
tx.setPosition(leash, 0, 0);
tx.setWindowCrop(leash, 0, 0);
} else {
- getSurfaceTransactionHelper().crop(tx, leash, destBounds);
+ getSurfaceTransactionHelper().cropAndPosition(tx, leash, destBounds);
}
if (mContentOverlay != null) {
clearContentOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3d1994cac534..b02bd0ffec6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -16,8 +16,10 @@
package com.android.wm.shell.pip;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.Choreographer;
@@ -68,52 +70,102 @@ public class PipSurfaceTransactionHelper {
* Operates the crop (and position) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect destinationBounds) {
+ public PipSurfaceTransactionHelper cropAndPosition(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect destinationBounds) {
tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
.setPosition(leash, destinationBounds.left, destinationBounds.top);
return this;
}
/**
+ * Operates {@link SurfaceControl.Transaction#setCrop} on a given transaction and leash.
+ *
+ * @param tx the transaction to apply
+ * @param leash the leash to crop
+ * @param relativeDestinationBounds the bounds to crop, which is relative to the leash
+ * coordinate
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper crop(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect relativeDestinationBounds) {
+ tx.setCrop(leash, relativeDestinationBounds);
+ return this;
+ }
+
+ /**
* Operates the scale (setMatrix) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds) {
mTmpDestinationRectF.set(destinationBounds);
- return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */,
+ true /* shouldOffset */);
}
/**
* Operates the scale (setMatrix) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, RectF destinationBounds) {
- return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
+ public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+ @NonNull RectF destinationBounds) {
+ return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */,
+ true /* shouldOffset */);
}
/**
- * Operates the scale (setMatrix) on a given transaction and leash
+ * Operates the scale (setMatrix) on a given transaction and leash.
+ *
+ * @param shouldOffset {@code true} to offset the leash to (0, 0)
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, Rect destinationBounds, float degrees) {
+ public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+ @NonNull Rect destinationBounds, boolean shouldOffset) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */, shouldOffset);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash.
+ *
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+ @NonNull Rect destinationBounds, float degrees) {
+ return scale(tx, leash, sourceBounds, destinationBounds, degrees, true /* shouldOffset */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash.
+ *
+ * @param shouldOffset {@code true} to offset the leash to (0, 0)
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+ @NonNull Rect destinationBounds, float degrees, boolean shouldOffset) {
mTmpDestinationRectF.set(destinationBounds);
- return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees, shouldOffset);
}
/**
* Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
+ *
+ * @param shouldOffset {@code true} to offset the leash to (0, 0)
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceBounds, RectF destinationBounds, float degrees) {
+ public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+ @NonNull RectF destinationBounds, float degrees, boolean shouldOffset) {
mTmpSourceRectF.set(sourceBounds);
// We want the matrix to position the surface relative to the screen coordinates so offset
- // the source to 0,0
- mTmpSourceRectF.offsetTo(0, 0);
+ // the source to (0, 0) if {@code shouldOffset} is true.
+ if (shouldOffset) {
+ mTmpSourceRectF.offsetTo(0, 0);
+ }
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
mTmpTransform.postRotate(degrees,
@@ -123,17 +175,19 @@ public class PipSurfaceTransactionHelper {
}
/**
- * Operates the scale (setMatrix) on a given transaction and leash
+ * Operates the scale (setMatrix) on a given transaction and leash.
+ *
+ * @param leashOffset the offset of the leash bounds relative to the screen coordinate
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
- SurfaceControl leash, Rect sourceRectHint,
- Rect sourceBounds, Rect destinationBounds, Rect insets,
- boolean isInPipDirection, float fraction) {
+ public PipSurfaceTransactionHelper scaleAndCrop(@NonNull SurfaceControl.Transaction tx,
+ @NonNull SurfaceControl leash, @NonNull Rect sourceRectHint, @NonNull Rect sourceBounds,
+ @NonNull Rect destinationBounds, @NonNull Rect insets, boolean isInPipDirection,
+ float fraction, @NonNull Point leashOffset) {
mTmpDestinationRect.set(sourceBounds);
// Similar to {@link #scale}, we want to position the surface relative to the screen
- // coordinates so offset the bounds to 0,0
- mTmpDestinationRect.offsetTo(0, 0);
+ // coordinates so offset the bounds relative to the leash.
+ mTmpDestinationRect.offset(-leashOffset.x, -leashOffset.y);
mTmpDestinationRect.inset(insets);
// Scale to the bounds no smaller than the destination and offset such that the top/left
// of the scaled inset source rect aligns with the top/left of the destination bounds
@@ -152,13 +206,13 @@ public class PipSurfaceTransactionHelper {
scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
(float) destinationBounds.height() / sourceBounds.height());
}
- float left = destinationBounds.left - insets.left * scale;
- float top = destinationBounds.top - insets.top * scale;
+ float left = destinationBounds.left - mTmpDestinationRect.left * scale;
+ float top = destinationBounds.top - mTmpDestinationRect.top * scale;
if (scale == 1) {
// Work around the 1 pixel off error by rounding the position down at very beginning.
// We noticed such error from flicker tests, not visually.
- left = sourceBounds.left;
- top = sourceBounds.top;
+ left = leashOffset.x;
+ top = leashOffset.y;
}
mTmpTransform.setScale(scale, scale);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
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 b4e03299f14c..c4e63dfdade9 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
@@ -960,7 +960,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final SurfaceControl.Transaction boundsChangeTx =
mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
- .crop(boundsChangeTx, mLeash, destinationBounds)
+ .cropAndPosition(boundsChangeTx, mLeash, destinationBounds)
.round(boundsChangeTx, mLeash, true /* applyCornerRadius */);
mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
@@ -988,7 +988,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.resetScale(tx, mLeash, destinationBounds)
- .crop(tx, mLeash, destinationBounds)
+ .cropAndPosition(tx, mLeash, destinationBounds)
.round(tx, mLeash, isInPip());
// The animation is finished in the Launcher and here we directly apply the final touch.
applyEnterPipSyncTransaction(destinationBounds, () -> {
@@ -1525,7 +1525,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipBoundsState.setBounds(toBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
- .crop(tx, mLeash, toBounds)
+ .cropAndPosition(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
@@ -1628,7 +1628,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Rect destinationBounds) {
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
- .crop(tx, mLeash, destinationBounds)
+ .cropAndPosition(tx, mLeash, destinationBounds)
.resetScale(tx, mLeash, destinationBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
return tx;
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 6da39951efbe..28b91c6cb812 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
@@ -30,7 +30,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -45,6 +44,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_CLEANUP_PIP_EX
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -551,7 +551,7 @@ public class PipTransition extends PipTransitionController {
}
// Reset the scale with bounds change synchronously.
if (hasValidLeash) {
- mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
+ mSurfaceTransactionHelper.cropAndPosition(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
final Rect appBounds = mPipOrganizer.mAppBounds;
@@ -588,7 +588,8 @@ public class PipTransition extends PipTransitionController {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Destination bounds were changed during animation", TAG);
rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
- mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+ mSurfaceTransactionHelper.cropAndPosition(mFinishTransaction, leash,
+ finishBounds);
}
}
mFinishTransaction = null;
@@ -1068,6 +1069,11 @@ public class PipTransition extends PipTransitionController {
mPipBoundsState.mayUseCachedLauncherShelfHeight();
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
+ // The app bounds should offset relative to the task leash to make the center calculation
+ // correctly.
+ final Rect relativeAppBounds = new Rect(taskInfo.topActivityMainWindowFrame != null
+ ? taskInfo.topActivityMainWindowFrame : currentBounds);
+ relativeAppBounds.offset(-currentBounds.left, -currentBounds.top);
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = mPipOrganizer.takeSwipeSourceRectHint();
@@ -1089,6 +1095,8 @@ 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;
@@ -1119,8 +1127,8 @@ public class PipTransition extends PipTransitionController {
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
- animator.setAppIconContentOverlay(
- mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
+ animator.setAppIconContentOverlay(mContext, relativeAppBounds,
+ destinationBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
} else {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -1149,14 +1157,14 @@ public class PipTransition extends PipTransitionController {
animationDuration = 0;
}
mSurfaceTransactionHelper
- .crop(finishTransaction, leash, destinationBounds)
+ .cropAndPosition(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
// Always reset to bounds animation type afterwards.
setEnterAnimationType(ANIM_TYPE_BOUNDS);
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
- mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
+ mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), relativeAppBounds);
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(animationDuration);
@@ -1340,11 +1348,11 @@ public class PipTransition extends PipTransitionController {
"%s: Update pip for unhandled transition, change=%s, destBounds=%s, isInPip=%b",
TAG, pipChange, destBounds, isInPip);
mSurfaceTransactionHelper
- .crop(startTransaction, leash, destBounds)
+ .cropAndPosition(startTransaction, leash, destBounds)
.round(startTransaction, leash, isInPip)
.shadow(startTransaction, leash, isInPip);
mSurfaceTransactionHelper
- .crop(finishTransaction, leash, destBounds)
+ .cropAndPosition(finishTransaction, leash, destBounds)
.round(finishTransaction, leash, isInPip)
.shadow(finishTransaction, leash, isInPip);
// Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
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 94b344fb575a..5ffc64f412f1 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
@@ -368,10 +368,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Finish the current transition if possible.
- *
- * @param tx transaction to be applied with a potentially new draw after finishing.
*/
- public void finishTransition(@Nullable SurfaceControl.Transaction tx) {
+ public void finishTransition() {
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 7a0e6694cb51..d3ae411469cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -23,7 +23,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.transitTypeToString;
import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
@@ -35,6 +34,7 @@ import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -338,7 +338,7 @@ public class TvPipTransition extends PipTransitionController {
final Rect pipBounds = mPipBoundsState.getBounds();
mSurfaceTransactionHelper
.resetScale(startTransaction, pipLeash, pipBounds)
- .crop(startTransaction, pipLeash, pipBounds)
+ .cropAndPosition(startTransaction, pipLeash, pipBounds)
.shadow(startTransaction, pipLeash, false);
final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
@@ -420,7 +420,7 @@ public class TvPipTransition extends PipTransitionController {
mSurfaceTransactionHelper
.resetScale(finishTransaction, leash, pipBounds)
- .crop(finishTransaction, leash, pipBounds)
+ .cropAndPosition(finishTransaction, leash, pipBounds)
.shadow(finishTransaction, leash, false);
final Rect currentBounds = pipChange.getStartAbsBounds();
@@ -443,7 +443,7 @@ public class TvPipTransition extends PipTransitionController {
SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.resetScale(tx, leash, pipBounds)
- .crop(tx, leash, pipBounds)
+ .cropAndPosition(tx, leash, pipBounds)
.shadow(tx, leash, false);
mShellTaskOrganizer.applyTransaction(resizePipWct);
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index 895c2aeba9ef..63c151268bdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.animation;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
@@ -25,6 +26,7 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
@@ -34,8 +36,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Animator that handles the alpha animation for entering PIP
*/
-public class PipAlphaAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener,
- ValueAnimator.AnimatorListener {
+public class PipAlphaAnimator extends ValueAnimator {
@IntDef(prefix = {"FADE_"}, value = {
FADE_IN,
FADE_OUT
@@ -47,15 +48,45 @@ public class PipAlphaAnimator extends ValueAnimator implements ValueAnimator.Ani
public static final int FADE_IN = 0;
public static final int FADE_OUT = 1;
- private final int mEnterAnimationDuration;
private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final float alpha = (Float) animation.getAnimatedValue();
+ mSurfaceControlTransactionFactory.getTransaction()
+ .setAlpha(mLeash, alpha).apply();
+ }
+ };
+
// optional callbacks for tracking animation start and end
@Nullable private Runnable mAnimationStartCallback;
@Nullable private Runnable mAnimationEndCallback;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ @NonNull private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
public PipAlphaAnimator(Context context,
@@ -71,11 +102,11 @@ public class PipAlphaAnimator extends ValueAnimator implements ValueAnimator.Ani
}
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
- mEnterAnimationDuration = context.getResources()
+ final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
- setDuration(mEnterAnimationDuration);
- addListener(this);
- addUpdateListener(this);
+ setDuration(enterAnimationDuration);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -86,32 +117,9 @@ public class PipAlphaAnimator extends ValueAnimator implements ValueAnimator.Ani
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- mStartTransaction.apply();
- }
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
}
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final float alpha = (Float) animation.getAnimatedValue();
- mSurfaceControlTransactionFactory.getTransaction().setAlpha(mLeash, alpha).apply();
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index 740b9af56144..e5137582822d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -192,22 +192,7 @@ public class PipEnterAnimator extends ValueAnimator
* calculated differently from generic transitions.
* @param pipChange PiP change received as a transition target.
*/
- public void setEnterStartState(@NonNull TransitionInfo.Change pipChange,
- @NonNull TransitionInfo.Change pipActivityChange) {
- PipUtils.calcEndTransform(pipActivityChange, pipChange, mInitActivityScale,
- mInitActivityPos);
- if (mStartTransaction != null && pipActivityChange.getLeash() != null) {
- mStartTransaction.setCrop(pipActivityChange.getLeash(), null);
- mStartTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x,
- mInitActivityScale.y);
- mStartTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x,
- mInitActivityPos.y);
- mFinishTransaction.setCrop(pipActivityChange.getLeash(), null);
- mFinishTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x,
- mInitActivityScale.y);
- mFinishTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x,
- mInitActivityPos.y);
- }
+ public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) {
PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop);
}
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 a93ef12cb7fa..3f9b0c30e314 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.animation;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -27,6 +28,7 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -34,8 +36,7 @@ import com.android.wm.shell.shared.animation.Interpolators;
/**
* Animator that handles bounds animations for exit-via-expanding PIP.
*/
-public class PipExpandAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipExpandAnimator extends ValueAnimator {
@NonNull
private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
@@ -58,12 +59,61 @@ public class PipExpandAnimator extends ValueAnimator
// Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
private final RectEvaluator mRectEvaluator;
private final RectEvaluator mInsetEvaluator;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ 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 */);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ 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 */);
+ tx.apply();
+ }
+ };
+
public PipExpandAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
@@ -105,8 +155,8 @@ public class PipExpandAnimator extends ValueAnimator
setObjectValues(startBounds, endBounds);
setEvaluator(mRectEvaluator);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -117,58 +167,15 @@ public class PipExpandAnimator extends ValueAnimator
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- mStartTransaction.apply();
- }
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator 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 */);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- 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 */);
- tx.apply();
- }
-
private Rect getInsets(float fraction) {
final Rect startInsets = mSourceRectHintInsets;
final Rect endInsets = mZeroInsets;
return mInsetEvaluator.evaluate(fraction, startInsets, endInsets);
}
- // no-ops
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
}
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 d565776c9917..012dabbbb9f8 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.animation;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -27,13 +28,13 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
/**
* Animator that handles any resize related animation for PIP.
*/
-public class PipResizeAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener{
+public class PipResizeAnimator extends ValueAnimator {
@NonNull
private final Context mContext;
@NonNull
@@ -61,9 +62,47 @@ public class PipResizeAnimator extends ValueAnimator
private final Rect mAnimatedRect = new Rect();
private final float mDelta;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTx != null) {
+ setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+ mStartTx.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mFinishTx != null) {
+ setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ final float degrees = (1.0f - fraction) * mDelta;
+ setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+ tx.apply();
+ }
+ };
+
public PipResizeAnimator(@NonNull Context context,
@NonNull SurfaceControl leash,
@Nullable SurfaceControl.Transaction startTransaction,
@@ -89,8 +128,8 @@ public class PipResizeAnimator extends ValueAnimator
mRectEvaluator = new RectEvaluator(mAnimatedRect);
setObjectValues(startBounds, endBounds);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
setEvaluator(mRectEvaluator);
setDuration(duration);
}
@@ -103,26 +142,6 @@ public class PipResizeAnimator extends ValueAnimator
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTx != null) {
- setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
- mStartTx.apply();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- final float fraction = getAnimatedFraction();
- final float degrees = (1.0f - fraction) * mDelta;
- setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
- tx.apply();
- }
-
/**
* Set a proper transform matrix for a leash to move it to given bounds with a certain rotation.
*
@@ -130,7 +149,7 @@ public class PipResizeAnimator extends ValueAnimator
* @param targetBounds bounds to which we are scaling the leash.
* @param degrees degrees of rotation - counter-clockwise is positive by convention.
*/
- public static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
+ private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect baseBounds, Rect targetBounds, float degrees) {
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@@ -144,19 +163,9 @@ public class PipResizeAnimator extends ValueAnimator
tx.setMatrix(leash, transformTensor, mMatrixTmp);
}
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mFinishTx != null) {
- setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(@NonNull
+ PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
}
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
}
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 8c1e5e6a3e84..58d2a8577d8c 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
@@ -243,7 +243,6 @@ public class PhonePipMenuController implements PipMenuController,
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, destinationBounds.width(),
destinationBounds.height()));
- updateMenuLayout(destinationBounds);
}
/**
@@ -569,23 +568,6 @@ public class PhonePipMenuController implements PipMenuController,
}
}
- /**
- * Tell the PIP Menu to recalculate its layout given its current position on the display.
- */
- public void updateMenuLayout(Rect bounds) {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateMenuLayout() state=%s"
- + " isMenuVisible=%s"
- + " callers=\n%s", TAG, mMenuState, isMenuVisible,
- Debug.getCallers(5, " "));
- }
- if (isMenuVisible) {
- mPipMenuView.updateMenuLayout(bounds);
- }
- }
-
@Override
public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState, Bundle extra) {
@@ -597,7 +579,6 @@ public class PhonePipMenuController implements PipMenuController,
detach();
break;
case PipTransitionState.CHANGED_PIP_BOUNDS:
- updateMenuLayout(mPipBoundsState.getBounds());
hideMenu();
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
deleted file mode 100644
index ecb6ad690e26..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip2.phone;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * Helper class to calculate and place the menu icons on the PIP Menu.
- */
-public class PipMenuIconsAlgorithm {
-
- private static final String TAG = "PipMenuIconsAlgorithm";
-
- protected ViewGroup mViewRoot;
- protected ViewGroup mTopEndContainer;
- protected View mDragHandle;
- protected View mSettingsButton;
- protected View mDismissButton;
-
- protected PipMenuIconsAlgorithm(Context context) {
- }
-
- /**
- * Bind the necessary views.
- */
- public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
- View settingsButton, View dismissButton) {
- mViewRoot = viewRoot;
- mTopEndContainer = topEndContainer;
- mDragHandle = dragHandle;
- mSettingsButton = settingsButton;
- mDismissButton = dismissButton;
- }
-
- /**
- * Updates the position of the drag handle based on where the PIP window is on the screen.
- */
- public void onBoundsChanged(Rect bounds) {
- // On phones, the menu icons are always static and will never move based on the PIP window
- // position. No need to do anything here.
- }
-
- /**
- * Set the gravity on the given view.
- */
- protected static void setLayoutGravity(View v, int gravity) {
- if (v.getLayoutParams() instanceof FrameLayout.LayoutParams) {
- FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
- params.gravity = gravity;
- v.setLayoutParams(params);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
index 0910919b3064..7cfaadedeca3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
@@ -145,7 +145,6 @@ public class PipMenuView extends FrameLayout {
protected View mSettingsButton;
protected View mDismissButton;
protected View mTopEndContainer;
- protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
// How long the shell will wait for the app to close the PiP if a custom action is set.
private final int mPipForceCloseDelay;
@@ -193,9 +192,6 @@ public class PipMenuView extends FrameLayout {
mActionsGroup = findViewById(R.id.actions_group);
mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
R.dimen.pip_between_action_padding_land);
- mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
- mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
- findViewById(R.id.resize_handle), mSettingsButton, mDismissButton);
mDismissFadeOutDurationMs = context.getResources()
.getInteger(R.integer.config_pipExitAnimationDuration);
@@ -339,10 +335,6 @@ public class PipMenuView extends FrameLayout {
cancelDelayedHide();
}
- void updateMenuLayout(Rect bounds) {
- mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
- }
-
void hideMenu() {
hideMenu(null);
}
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 810eff8a055c..17392bc521d8 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
@@ -752,11 +752,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION));
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
- SurfaceControl.Transaction startTx = extra.getParcelable(
+ final SurfaceControl.Transaction startTx = extra.getParcelable(
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishTx = extra.getParcelable(
+ final SurfaceControl.Transaction finishTx = extra.getParcelable(
PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
- Rect destinationBounds = extra.getParcelable(
+ final Rect destinationBounds = extra.getParcelable(
PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
@@ -794,7 +794,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
cleanUpHighPerfSessionMaybe();
// Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(destinationBounds, false /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds);
}
private void startResizeAnimation(SurfaceControl.Transaction startTx,
@@ -803,11 +803,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
Preconditions.checkState(pipLeash != null,
"No leash cached by mPipTransitionState=" + mPipTransitionState);
- startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
- mPipBoundsState.getBounds().height());
-
PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
- startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+ startTx, finishTx, destinationBounds, mPipBoundsState.getBounds(),
destinationBounds, duration, 0f /* angle */);
animator.setAnimationEndCallback(() -> {
// In case an ongoing drag/fling was present before a deterministic resize transition
@@ -818,7 +815,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(destinationBounds, true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds);
});
animator.start();
}
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 f5ef64dff94b..751175f0f3e9 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
@@ -535,28 +535,26 @@ public class PipResizeGestureHandler implements
Preconditions.checkState(pipLeash != null,
"No leash cached by mPipTransitionState=" + mPipTransitionState);
- SurfaceControl.Transaction startTx = extra.getParcelable(
+ final SurfaceControl.Transaction startTx = extra.getParcelable(
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishTx = extra.getParcelable(
+ final SurfaceControl.Transaction finishTx = extra.getParcelable(
PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+ final Rect destinationBounds = extra.getParcelable(
+ PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
- startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
- mPipBoundsState.getBounds().height());
-
PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
- startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
- mLastResizeBounds, duration, mAngle);
+ startTx, finishTx, destinationBounds, mStartBoundsAfterRelease,
+ destinationBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
- mUserResizeBounds.set(mLastResizeBounds);
+ mUserResizeBounds.set(destinationBounds);
resetState();
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
- mPipScheduler.scheduleFinishResizePip(
- mLastResizeBounds, true /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds);
});
animator.start();
break;
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 fbbf6f3596d0..8b25b11e3a47 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
@@ -175,22 +175,12 @@ public class PipScheduler {
* Note that we do not allow any actual WM Core changes at this point.
*
* @param toBounds destination bounds used only for internal state updates - not sent to Core.
- * @param configAtEnd true if we are waiting for config updates at the end of the transition.
*/
- public void scheduleFinishResizePip(Rect toBounds, boolean configAtEnd) {
- // Make updates to the internal state to reflect new bounds
+ public void scheduleFinishResizePip(Rect toBounds) {
+ // Make updates to the internal state to reflect new bounds before updating any transitions
+ // related state; transition state updates can trigger callbacks that use the cached bounds.
onFinishingPipResize(toBounds);
-
- SurfaceControl.Transaction tx = null;
- if (configAtEnd) {
- tx = new SurfaceControl.Transaction();
- tx.addTransactionCommittedListener(mMainExecutor, () -> {
- mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
- });
- } else {
- mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
- }
- mPipTransitionController.finishTransition(tx);
+ mPipTransitionController.finishTransition();
}
/**
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 373ec806c40c..2c7584af1f07 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
@@ -177,8 +177,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
mPipBoundsState.getBounds(), destinationBounds, duration,
0f /* delta */);
animator.setAnimationEndCallback(() -> {
- mPipScheduler.scheduleFinishResizePip(
- destinationBounds, false /* configAtEnd */);
+ mPipScheduler.scheduleFinishResizePip(destinationBounds);
});
animator.start();
}
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 e90b32cd01e7..64d8887ae5cd 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
@@ -35,6 +35,7 @@ import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -276,21 +277,25 @@ public class PipTransition extends PipTransitionController implements
if (pipChange == null) {
return false;
}
- SurfaceControl pipLeash = pipChange.getLeash();
+ // We expect the PiP activity as a separate change in a config-at-end transition;
+ // only flings are not using config-at-end for resize bounds changes
+ TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
+ pipChange.getTaskInfo().getToken());
+ if (pipActivityChange != null) {
+ // Transform calculations use PiP params by default, so make sure they are null to
+ // default to using bounds for scaling calculations instead.
+ pipChange.getTaskInfo().pictureInPictureParams = null;
+ prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
+ pipActivityChange);
+ }
- // Even though the final bounds and crop are applied with finishTransaction since
- // this is a visible change, we still need to handle the app draw coming in. Snapshot
- // covering app draw during collection will be removed by startTransaction. So we make
- // the crop equal to the final bounds and then let the current
- // animator scale the leash back to starting bounds.
- // Note: animator is responsible for applying the startTx but NOT finishTx.
+ SurfaceControl pipLeash = pipChange.getLeash();
startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
pipChange.getEndAbsBounds().height());
- // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
// Classes interested in continuing the animation would subscribe to this state update
- // getting info such as endBounds, startTx, and finishTx as an extra Bundle once
- // animators are in place. Once done state needs to be updated to CHANGED_PIP_BOUNDS.
+ // getting info such as endBounds, startTx, and finishTx as an extra Bundle
+ // Once done state needs to be updated to CHANGED_PIP_BOUNDS via {@link PipScheduler#}.
Bundle extra = new Bundle();
extra.putParcelable(PIP_START_TX, startTransaction);
extra.putParcelable(PIP_FINISH_TX, finishTransaction);
@@ -353,10 +358,12 @@ public class PipTransition extends PipTransitionController implements
sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
}
+ prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
+ pipActivityChange);
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
- animator.setEnterStartState(pipChange, pipActivityChange);
+ animator.setEnterStartState(pipChange);
animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
startTransaction.apply();
@@ -368,7 +375,7 @@ public class PipTransition extends PipTransitionController implements
tx.apply();
});
}
- finishInner();
+ finishTransition();
return true;
}
@@ -393,14 +400,13 @@ public class PipTransition extends PipTransitionController implements
final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
-
final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
endBounds);
-
- final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
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.
@@ -435,14 +441,16 @@ public class PipTransition extends PipTransitionController implements
mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
}
- animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange,
- pipActivityChange));
+
+ prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
+ pipActivityChange);
+ animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
animator.setAnimationEndCallback(() -> {
if (animator.getContentOverlayLeash() != null) {
startOverlayFadeoutAnimation(animator.getContentOverlayLeash(),
animator::clearAppIconOverlay);
}
- finishInner();
+ finishTransition();
});
animator.start();
return true;
@@ -512,8 +520,7 @@ public class PipTransition extends PipTransitionController implements
PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
PipAlphaAnimator.FADE_IN);
// This should update the pip transition state accordingly after we stop playing.
- animator.setAnimationEndCallback(this::finishInner);
-
+ animator.setAnimationEndCallback(this::finishTransition);
animator.start();
return true;
}
@@ -569,12 +576,7 @@ public class PipTransition extends PipTransitionController implements
PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
sourceRectHint, Surface.ROTATION_0);
-
- animator.setAnimationEndCallback(() -> {
- mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
- finishCallback.onTransitionFinished(null);
- });
-
+ animator.setAnimationEndCallback(this::finishTransition);
animator.start();
return true;
}
@@ -698,33 +700,54 @@ public class PipTransition extends PipTransitionController implements
return isPipMovedToBack || isPipClosed || isPipDismissed;
}
+ private void prepareConfigAtEndActivity(@NonNull SurfaceControl.Transaction startTx,
+ @NonNull SurfaceControl.Transaction finishTx,
+ @NonNull TransitionInfo.Change pipChange,
+ @NonNull TransitionInfo.Change pipActivityChange) {
+ PointF initActivityScale = new PointF();
+ PointF initActivityPos = new PointF();
+ PipUtils.calcEndTransform(pipActivityChange, pipChange, initActivityScale,
+ initActivityPos);
+ if (pipActivityChange.getLeash() != null) {
+ startTx.setCrop(pipActivityChange.getLeash(), null);
+ startTx.setScale(pipActivityChange.getLeash(), initActivityScale.x,
+ initActivityScale.y);
+ startTx.setPosition(pipActivityChange.getLeash(), initActivityPos.x,
+ initActivityPos.y);
+
+ finishTx.setCrop(pipActivityChange.getLeash(), null);
+ finishTx.setScale(pipActivityChange.getLeash(), initActivityScale.x,
+ initActivityScale.y);
+ finishTx.setPosition(pipActivityChange.getLeash(), initActivityPos.x,
+ initActivityPos.y);
+ }
+ }
+
//
// Miscellaneous callbacks and listeners
//
- private void finishInner() {
- finishTransition(null /* tx */);
- if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
- // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
- // and then we get a signal on client finishing its draw after the transition
- // has ended, then we have fully entered PiP.
- mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
- }
- }
-
@Override
- public void finishTransition(@Nullable SurfaceControl.Transaction tx) {
- WindowContainerTransaction wct = null;
- if (tx != null && mPipTransitionState.mPipTaskToken != null) {
- // Outside callers can only provide a transaction to be applied with the final draw.
- // So no actual WM changes can be applied for this transition after this point.
- wct = new WindowContainerTransaction();
- wct.setBoundsChangeTransaction(mPipTransitionState.mPipTaskToken, tx);
- }
+ public void finishTransition() {
if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(wct);
+ mFinishCallback.onTransitionFinished(null /* finishWct */);
mFinishCallback = null;
}
+
+ final int currentState = mPipTransitionState.getState();
+ int nextState = PipTransitionState.UNDEFINED;
+ switch (currentState) {
+ case PipTransitionState.ENTERING_PIP:
+ nextState = PipTransitionState.ENTERED_PIP;
+ break;
+ case PipTransitionState.CHANGING_PIP_BOUNDS:
+ nextState = PipTransitionState.CHANGED_PIP_BOUNDS;
+ break;
+ case PipTransitionState.EXITING_PIP:
+ nextState = PipTransitionState.EXITED_PIP;
+ break;
+ }
+ mPipTransitionState.setState(nextState);
}
@Override
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 245829ecafb3..371bdd5c6469 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
@@ -45,4 +45,7 @@ oneway interface IRecentTasksListener {
/** A task has moved to front. */
oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+
+ /** A task info has changed. */
+ oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
} \ No newline at end of file
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 6086801491e2..faa20159f64a 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
@@ -47,7 +47,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -289,6 +288,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
+ public void onTaskChangedThroughTransition(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ notifyTaskInfoChanged(taskInfo);
+ }
+
+ @Override
public void onTaskMovedToFrontThroughTransition(
ActivityManager.RunningTaskInfo runningTaskInfo) {
notifyTaskMovedToFront(runningTaskInfo);
@@ -355,6 +359,19 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
+ private void notifyTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null
+ || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
+ || taskInfo.realActivity == null) {
+ return;
+ }
+ try {
+ mListener.onTaskInfoChanged(taskInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call onTaskInfoChanged", e);
+ }
+ }
+
private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
if (mListener == null
|| !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
@@ -426,7 +443,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
// If task has their app bounds set to null which happens after reboot, set the
// app bounds to persisted lastFullscreenBounds. Also set the position in parent
// to the top left of the bounds.
- if (Flags.enableDesktopWindowingPersistence()
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()
&& taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
taskInfo.configuration.windowConfiguration.setAppBounds(
taskInfo.lastNonFullscreenBounds);
@@ -636,6 +653,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
mListener.call(l -> l.onTaskMovedToFront(taskInfo));
}
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mListener.call(l -> l.onTaskInfoChanged(taskInfo));
+ }
};
public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 1af99f974a28..d28a462546f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -20,8 +20,9 @@ import android.app.ActivityManager.RunningTaskInfo
import android.os.IBinder
import android.util.ArrayMap
import android.view.SurfaceControl
-import android.window.TransitionInfo
+import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopModeFlags
+import android.window.TransitionInfo
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -69,8 +70,10 @@ class TaskStackTransitionObserver(
// Find the first task that is opening, this should be the one at the front after
// the transition
if (TransitionUtil.isOpeningType(change.mode)) {
- notifyTaskStackTransitionObserverListeners(taskInfo)
+ notifyOnTaskMovedToFront(taskInfo)
break
+ } else if (change.mode == TRANSIT_CHANGE) {
+ notifyOnTaskChanged(taskInfo)
}
}
}
@@ -95,15 +98,23 @@ class TaskStackTransitionObserver(
taskStackTransitionObserverListeners.remove(taskStackTransitionObserverListener)
}
- private fun notifyTaskStackTransitionObserverListeners(taskInfo: RunningTaskInfo) {
+ private fun notifyOnTaskMovedToFront(taskInfo: RunningTaskInfo) {
taskStackTransitionObserverListeners.forEach { (listener, executor) ->
executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) }
}
}
+ private fun notifyOnTaskChanged(taskInfo: RunningTaskInfo) {
+ taskStackTransitionObserverListeners.forEach { (listener, executor) ->
+ executor.execute { listener.onTaskChangedThroughTransition(taskInfo) }
+ }
+ }
+
/** Listener to use to get updates regarding task stack from this observer */
interface TaskStackTransitionObserverListener {
/** Called when a task is moved to front. */
fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {}
+ /** Called when a task info has changed. */
+ fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7893267d014a..cc0e1df115c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -30,7 +30,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -73,6 +72,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1625,6 +1625,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
leftTopTaskId = mainStageTopTaskId;
rightBottomTaskId = sideStageTopTaskId;
}
+
+ if (Flags.enableFlexibleTwoAppSplit()) {
+ // Split screen can be laid out in such a way that some of the apps are offscreen.
+ // For the purposes of passing SplitBounds up to launcher (for use in thumbnails
+ // etc.), we crop the bounds down to the screen size.
+ topLeftBounds.left =
+ Math.max(topLeftBounds.left, 0);
+ topLeftBounds.top =
+ Math.max(topLeftBounds.top, 0);
+ bottomRightBounds.right =
+ Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
+ bottomRightBounds.top =
+ Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+ }
+
SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 0d89f757903e..17483dd68632 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,6 +40,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.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
@@ -419,7 +420,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
for (int i = 0; i < info.getRootCount(); ++i) {
out.addRoot(info.getRoot(i));
}
- out.setAnimationOptions(info.getAnimationOptions());
+ if (!Flags.moveAnimationOptionsToChange()) {
+ out.setAnimationOptions(info.getAnimationOptions());
+ }
return out;
}
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 5437167f58d5..ec58292b352c 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
@@ -561,13 +561,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
- options = info.getAnimationOptions();
- } else {
options = change.getAnimationOptions();
+ } else {
+ options = info.getAnimationOptions();
}
if (options != null) {
- attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
- cornerRadius);
+ attachThumbnail(animations, onAnimFinish, change, options, cornerRadius);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a27c14bda15a..4feb4753096e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,7 +24,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -34,6 +33,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.annotation.ColorInt;
import android.annotation.NonNull;
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 7c9cd0862b69..1d456aed5f4d 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
@@ -29,7 +29,6 @@ import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
-import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -725,7 +724,7 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
- info.getDebugId(), transitionToken, info);
+ info.getDebugId(), transitionToken, info.toString(" " /* prefix */));
int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
final ActiveTransition existing = mKnownTransitions.get(transitionToken);
@@ -1847,6 +1846,40 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
+ /**
+ * Like WindowManager#transitTypeToString(), but also covers known custom transition types as
+ * well.
+ */
+ public static String transitTypeToString(int transitType) {
+ if (transitType < TRANSIT_FIRST_CUSTOM) {
+ return WindowManager.transitTypeToString(transitType);
+ }
+
+ String typeStr = switch (transitType) {
+ case TRANSIT_EXIT_PIP -> "EXIT_PIP";
+ case TRANSIT_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+ case TRANSIT_REMOVE_PIP -> "REMOVE_PIP";
+ case TRANSIT_SPLIT_SCREEN_PAIR_OPEN -> "SPLIT_SCREEN_PAIR_OPEN";
+ case TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE -> "SPLIT_SCREEN_OPEN_TO_SIDE";
+ case TRANSIT_SPLIT_DISMISS_SNAP -> "SPLIT_DISMISS_SNAP";
+ case TRANSIT_SPLIT_DISMISS -> "SPLIT_DISMISS";
+ case TRANSIT_MAXIMIZE -> "MAXIMIZE";
+ case TRANSIT_RESTORE_FROM_MAXIMIZE -> "RESTORE_FROM_MAXIMIZE";
+ case TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP -> "DESKTOP_MODE_START_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> "DESKTOP_MODE_END_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP ->
+ "DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP";
+ case TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE -> "DESKTOP_MODE_TOGGLE_RESIZE";
+ case TRANSIT_RESIZE_PIP -> "RESIZE_PIP";
+ case TRANSIT_TASK_FRAGMENT_DRAG_RESIZE -> "TASK_FRAGMENT_DRAG_RESIZE";
+ case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
+ case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
+ case TRANSIT_MINIMIZE -> "MINIMIZE";
+ default -> "";
+ };
+ return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
+ }
+
private static boolean getShellTransitEnabled() {
try {
if (AppGlobals.getPackageManager().hasSystemFeature(
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 3946b6173b0b..c9546731a193 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
@@ -180,12 +180,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
// all other cases, it is expected that the transition handler positions and crops the task
// in order to allow the handler time to animate before the task before the final
// position and crop are set.
- final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
+ final boolean shouldSetTaskVisibilityPositionAndCrop =
+ mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
- shouldSetTaskPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
}
@VisibleForTesting
@@ -193,7 +194,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw,
- boolean setTaskCropAndPosition,
+ boolean shouldSetTaskVisibilityPositionAndCrop,
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
InsetsState displayInsetsState,
@@ -206,7 +207,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+ relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
@@ -234,7 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@SuppressLint("MissingPermission")
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus) {
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -246,7 +247,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
- setTaskCropAndPosition, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
+ shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+ mIsKeyguardVisibleAndOccluded,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
index e71b4f3abf14..7b71e41874c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
@@ -53,11 +53,8 @@ class DesktopHandleManageWindowsMenu(
private var menuViewContainer: AdditionalViewContainer? = null
init {
- show(snapshotList, onIconClickListener, onOutsideClickListener)
- }
-
- override fun close() {
- menuViewContainer?.releaseView()
+ createMenu(snapshotList, onIconClickListener, onOutsideClickListener)
+ animateOpen()
}
private fun calculateMenuPosition(): Point {
@@ -106,4 +103,8 @@ class DesktopHandleManageWindowsMenu(
view = menuView.rootView,
)
}
+
+ override fun removeFromContainer() {
+ menuViewContainer?.releaseView()
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index 173bc08970ca..dd68105d28c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -65,11 +65,10 @@ class DesktopHeaderManageWindowsMenu(
var menuViewContainer: AdditionalViewContainer? = null
init {
- show(snapshotList, onIconClickListener, onOutsideClickListener)
- }
-
- override fun close() {
- menuViewContainer?.releaseView()
+ createMenu(snapshotList, onIconClickListener, onOutsideClickListener)
+ menuView.rootView.pivotX = 0f
+ menuView.rootView.pivotY = 0f
+ animateOpen()
}
override fun addToContainer(menuView: ManageWindowsView) {
@@ -139,4 +138,8 @@ class DesktopHeaderManageWindowsMenu(
surfaceControlTransactionSupplier
)
}
+
+ override fun removeFromContainer() {
+ menuViewContainer?.releaseView()
+ }
}
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 a3324cc6f286..29b8ddd03970 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
@@ -130,7 +130,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -181,7 +180,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
- private DesktopStatusBarInputLayerSupplier mStatusBarInputLayerSupplier;
private final ExclusionRegionListener mExclusionRegionListener =
new ExclusionRegionListenerImpl();
@@ -420,11 +418,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return Unit.INSTANCE;
});
}
- if (Flags.enableHandleInputFix()) {
- mStatusBarInputLayerSupplier =
- new DesktopStatusBarInputLayerSupplier(mContext, mMainHandler);
- mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
- }
+ mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
}
@Override
@@ -480,7 +474,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
- decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
mActivityOrientationChangeHandler.ifPresent(handler ->
handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
@@ -519,7 +512,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -673,7 +665,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.closeHandleMenu();
// When the app enters split-select, the handle will no longer be visible, meaning
// we shouldn't receive input for it any longer.
- decoration.detachStatusBarInputLayer();
+ decoration.disposeStatusBarInputLayer();
mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
}
@@ -1314,8 +1306,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// should not be receiving any input.
if (resultType == TO_SPLIT_LEFT_INDICATOR
|| resultType == TO_SPLIT_RIGHT_INDICATOR) {
- relevantDecor.detachStatusBarInputLayer();
- // We should also detach the other split task's input layer if
+ relevantDecor.disposeStatusBarInputLayer();
+ // We should also dispose the other split task's input layer if
// applicable.
final int splitPosition = mSplitScreenController
.getSplitPosition(relevantDecor.mTaskInfo.taskId);
@@ -1328,7 +1320,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mSplitScreenController.getTaskInfo(oppositePosition);
if (oppositeTaskInfo != null) {
mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .detachStatusBarInputLayer();
+ .disposeStatusBarInputLayer();
}
}
}
@@ -1578,7 +1570,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(taskPositioner);
- windowDecoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -1587,18 +1578,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
}
- /** Decide which cached status bar input layer should be used for a decoration. */
- private AdditionalSystemViewContainer getStatusBarInputLayer(
- RunningTaskInfo taskInfo
- ) {
- if (mStatusBarInputLayerSupplier == null) return null;
- return mStatusBarInputLayerSupplier.getStatusBarInputLayer(
- taskInfo,
- mSplitScreenController.getSplitPosition(taskInfo.taskId),
- mSplitScreenController.isLeftRightSplit()
- );
- }
-
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
.getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
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 dc27cfe9e35f..c88ac3d19635 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
@@ -106,7 +106,6 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -206,7 +205,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopRepository mDesktopRepository;
- private AdditionalSystemViewContainer mStatusBarInputLayer;
DesktopModeWindowDecoration(
Context context,
@@ -392,18 +390,25 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
- // 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
- // in order to allow the handler time to animate before the task before the final
- // position and crop are set.
- final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
- && mTaskDragResizer.isResizingOrAnimating();
+ // 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
+ // those task properties to allow the handler time to animate with full control of the task
+ // leash. In general, allowing the window decoration to set any of these is likely to cause
+ // incorrect frames and flickering because relayouts from TaskListener#onTaskInfoChanged
+ // aren't synchronized with shell transition callbacks, so if they come too early it
+ // might show/hide or crop the task at a bad time.
+ // Fluid resizing is exempt from this because it intentionally doesn't use shell
+ // transitions to resize the task, so onTaskInfoChanged relayouts is the only way to make
+ // sure the crop is set correctly.
+ final boolean shouldSetTaskVisibilityPositionAndCrop =
+ !DesktopModeStatus.isVeiledResizeEnabled()
+ && mTaskDragResizer.isResizingOrAnimating();
// For headers only (i.e. in freeform): use |applyStartTransactionOnDraw| so that the
// transaction (that applies task crop) is synced with the buffer transaction (that draws
// the View). Both will be shown on screen at the same, whereas applying them independently
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
- relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop,
+ relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
hasGlobalFocus);
if (!applyTransactionOnDraw) {
t.apply();
@@ -430,19 +435,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus) {
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,
- shouldSetTaskPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
} 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,
- shouldSetTaskPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
}
Trace.endSection();
}
@@ -450,12 +455,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
/** Run the whole relayout phase immediately without delay. */
private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus) {
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
if (mResult.mRootView != null) {
updateViewHost(mRelayoutParams, startT, mResult);
}
@@ -477,7 +482,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus) {
if (applyStartTransactionOnDraw) {
throw new IllegalArgumentException(
@@ -486,7 +491,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop,
+ false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
hasGlobalFocus);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
@@ -501,7 +506,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@SuppressLint("MissingPermission")
private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
@@ -526,9 +531,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final boolean inFullImmersive = mDesktopRepository
.isTaskInFullImmersiveState(taskInfo.taskId);
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
- shouldSetTaskPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
- inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId),
- hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+ mIsKeyguardVisibleAndOccluded, inFullImmersive,
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -550,13 +555,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
notifyNoCaptionHandle();
}
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
- detachStatusBarInputLayer();
+ disposeStatusBarInputLayer();
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
return;
}
if (oldRootView != mResult.mRootView) {
- detachStatusBarInputLayer();
+ disposeStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
}
Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
@@ -574,9 +579,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
isCaptionVisible()
));
- if (mStatusBarInputLayer != null) {
- asAppHandle(mWindowDecorViewHolder).bindStatusBarInputLayer(mStatusBarInputLayer);
- }
} else {
mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
mTaskInfo,
@@ -790,15 +792,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
- * Detach the status bar input layer from this decoration. Intended to be
+ * Dispose of the view used to forward inputs in status bar region. Intended to be
* used any time handle is no longer visible.
*/
- void detachStatusBarInputLayer() {
+ void disposeStatusBarInputLayer() {
if (!isAppHandle(mWindowDecorViewHolder)
|| !Flags.enableHandleInputFix()) {
return;
}
- asAppHandle(mWindowDecorViewHolder).detachStatusBarInputLayer();
+ asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
}
private WindowDecorationViewHolder createViewHolder() {
@@ -857,7 +859,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Context context,
ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw,
- boolean shouldSetTaskPositionAndCrop,
+ boolean shouldSetTaskVisibilityPositionAndCrop,
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
@@ -953,7 +955,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
: R.dimen.freeform_decor_shadow_unfocused_thickness;
}
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+ relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
// The configuration used to layout the window decoration. A copy is made instead of using
// the original reference so that the configuration isn't mutated on config changes and
@@ -1094,13 +1096,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (mAppIconBitmap != null && mAppName != null) {
return;
}
- final ComponentName baseActivity = mTaskInfo.baseActivity;
- if (baseActivity == null) {
- Slog.e(TAG, "Base activity component not found in task");
+ if (mTaskInfo.baseIntent == null) {
+ Slog.e(TAG, "Base intent not found in task");
return;
}
final PackageManager pm = mUserContext.getPackageManager();
- final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
+ final ActivityInfo activityInfo =
+ pm.getActivityInfo(mTaskInfo.baseIntent.getComponent(), 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
@@ -1452,7 +1454,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void closeManageWindowsMenu() {
if (mManageWindowsMenu != null) {
- mManageWindowsMenu.close();
+ mManageWindowsMenu.animateClose();
}
mManageWindowsMenu = null;
}
@@ -1638,7 +1640,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeManageWindowsMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
- detachStatusBarInputLayer();
+ disposeStatusBarInputLayer();
clearCurrentViewHostRunnable();
if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
notifyNoCaptionHandle();
@@ -1755,16 +1757,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
+ "}";
}
- /**
- * Set the view container to be used to forward input through status bar. Null in cases
- * where input forwarding isn't needed.
- */
- public void setStatusBarInputLayer(
- @Nullable AdditionalSystemViewContainer additionalSystemViewContainer
- ) {
- mStatusBarInputLayer = additionalSystemViewContainer;
- }
-
static class Factory {
DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
deleted file mode 100644
index 025bb403ec8b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
+++ /dev/null
@@ -1,117 +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.android.wm.shell.windowdecor
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
-import android.content.Context
-import android.graphics.PixelFormat
-import android.os.Handler
-import android.view.Gravity
-import android.view.View
-import android.view.WindowManager
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.shared.split.SplitScreenConstants
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-
-/**
- * Supplier for [AdditionalSystemViewContainer] objects to be used for forwarding input
- * events through status bar to an app handle. Currently supports two simultaneous input layers.
- *
- * The supplier will pick one of two input layer view containers to use: one for tasks in
- * fullscreen or top/left split stage, and one for tasks in right split stage.
- */
-class DesktopStatusBarInputLayerSupplier(
- private val context: Context,
- @ShellMainThread handler: Handler
-) {
- private val inputLayers: MutableList<AdditionalSystemViewContainer> = mutableListOf()
-
- init {
- // Post this as creation of the input layer views is a relatively expensive operation.
- handler.post {
- repeat(TOTAL_INPUT_LAYERS) {
- inputLayers.add(createInputLayer())
- }
- }
- }
-
- private fun createInputLayer(): AdditionalSystemViewContainer {
- val lp = WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSPARENT
- )
- lp.title = "Desktop status bar input layer"
- lp.gravity = Gravity.LEFT or Gravity.TOP
- lp.setTrustedOverlay()
-
- // Make this window a spy window to enable it to pilfer pointers from the system-wide
- // gesture listener that receives events before window. This is to prevent notification
- // shade gesture when we swipe down to enter desktop.
- lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
- lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- val view = View(context)
- view.visibility = View.INVISIBLE
- return AdditionalSystemViewContainer(
- WindowManagerWrapper(
- context.getSystemService<WindowManager>(WindowManager::class.java)
- ),
- view,
- lp
- )
- }
-
- /**
- * Decide which cached status bar input layer should be used for a decoration, if any.
- *
- * [splitPosition] and [isLeftRightSplit] are used to determine which input layer we use.
- * The first one is reserved for fullscreen tasks or tasks in top/left split,
- * while the second one is exclusively used for tasks in right split stage. Note we care about
- * left-right vs top-bottom split as the bottom stage should not use an input layer.
- */
- fun getStatusBarInputLayer(
- taskInfo: RunningTaskInfo,
- @SplitScreenConstants.SplitPosition splitPosition: Int,
- isLeftRightSplit: Boolean
- ): AdditionalSystemViewContainer? {
- if (!taskInfo.isVisibleRequested) return null
- // Fullscreen and top/left split tasks will use the first input layer.
- if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
- || splitPosition == SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
- ) {
- return inputLayers[LEFT_TOP_INPUT_LAYER]
- }
- // Right split tasks will use the second one.
- if (isLeftRightSplit && splitPosition == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
- ) {
- return inputLayers[RIGHT_SPLIT_INPUT_LAYER]
- }
- // Which leaves bottom split and freeform tasks, which do not need an input layer
- // as the status bar is not blocking them.
- return null
- }
-
- companion object {
- private const val TOTAL_INPUT_LAYERS = 2
- // Input layer index for fullscreen tasks and tasks in top-left split
- private const val LEFT_TOP_INPUT_LAYER = 0
- // Input layer index for tasks in right split stage. Does not include bottom split as that
- // stage is not blocked by status bar.
- private const val RIGHT_SPLIT_INPUT_LAYER = 1
- }
-}
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 f97dfb89bc0d..b016c755e323 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
@@ -249,7 +249,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (!mTaskInfo.isVisible) {
releaseViews(wct);
- finishT.hide(mTaskSurface);
+ if (params.mSetTaskVisibilityPositionAndCrop) {
+ finishT.hide(mTaskSurface);
+ }
return;
}
@@ -422,7 +424,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) {
- if (params.mSetTaskPositionAndCrop) {
+ if (params.mSetTaskVisibilityPositionAndCrop) {
final Point taskPosition = mTaskInfo.positionInParent;
startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
@@ -437,9 +439,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
shadowRadius =
loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
}
- startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface);
+ startT.setShadowRadius(mTaskSurface, shadowRadius);
finishT.setShadowRadius(mTaskSurface, shadowRadius);
+ if (params.mSetTaskVisibilityPositionAndCrop) {
+ startT.show(mTaskSurface);
+ }
+
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
// When fluid resize is enabled, add a background to freeform tasks
@@ -758,7 +764,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
- boolean mSetTaskPositionAndCrop;
+ boolean mSetTaskVisibilityPositionAndCrop;
boolean mHasGlobalFocus;
void reset() {
@@ -777,7 +783,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mIsCaptionVisible = false;
mApplyStartTransactionOnDraw = false;
- mSetTaskPositionAndCrop = false;
+ mSetTaskVisibilityPositionAndCrop = false;
mWindowDecorConfig = null;
mHasGlobalFocus = false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 1451f363ec73..8b6aaaf619e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -23,8 +23,8 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.View
+import android.view.WindowInsets
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams
import com.android.wm.shell.windowdecor.WindowManagerWrapper
/**
@@ -33,11 +33,27 @@ import com.android.wm.shell.windowdecor.WindowManagerWrapper
*/
class AdditionalSystemViewContainer(
private val windowManagerWrapper: WindowManagerWrapper,
- override val view: View,
- val lp: LayoutParams
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0,
+ override val view: View
) : AdditionalViewContainer() {
+ val lp: WindowManager.LayoutParams = WindowManager.LayoutParams(
+ width, height, x, y,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+ flags,
+ PixelFormat.TRANSPARENT
+ ).apply {
+ title = "Additional view container of Task=$taskId"
+ gravity = Gravity.LEFT or Gravity.TOP
+ setTrustedOverlay()
+ this.forciblyShownTypes = forciblyShownTypes
+ }
- /** Provide a layout id of a view to inflate for this view container. */
constructor(
context: Context,
windowManagerWrapper: WindowManagerWrapper,
@@ -50,30 +66,15 @@ class AdditionalSystemViewContainer(
@LayoutRes layoutId: Int
) : this(
windowManagerWrapper = windowManagerWrapper,
- view = LayoutInflater.from(context).inflate(layoutId, null /* parent */),
- lp = createLayoutParams(x, y, width, height, flags, taskId)
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
)
- /** Provide a view directly for this view container */
- constructor(
- windowManagerWrapper: WindowManagerWrapper,
- taskId: Int,
- x: Int,
- y: Int,
- width: Int,
- height: Int,
- flags: Int,
- view: View,
- forciblyShownTypes: Int = 0
- ) : this(
- windowManagerWrapper = windowManagerWrapper,
- view = view,
- lp = createLayoutParams(x, y, width, height, flags, taskId).apply {
- this.forciblyShownTypes = forciblyShownTypes
- }
- )
-
- /** Do not supply a view at all, instead creating the view container with a basic view. */
constructor(
context: Context,
windowManagerWrapper: WindowManagerWrapper,
@@ -85,7 +86,12 @@ class AdditionalSystemViewContainer(
flags: Int
) : this(
windowManagerWrapper = windowManagerWrapper,
- lp = createLayoutParams(x, y, width, height, flags, taskId),
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
view = View(context)
)
@@ -98,7 +104,7 @@ class AdditionalSystemViewContainer(
}
override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
- lp.apply {
+ val lp = (view.layoutParams as WindowManager.LayoutParams).apply {
this.x = x.toInt()
this.y = y.toInt()
}
@@ -118,29 +124,13 @@ class AdditionalSystemViewContainer(
): AdditionalSystemViewContainer =
AdditionalSystemViewContainer(
windowManagerWrapper = windowManagerWrapper,
- view = view,
- lp = createLayoutParams(x, y, width, height, flags, taskId)
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = view
)
}
- companion object {
- fun createLayoutParams(
- x: Int,
- y: Int,
- width: Int,
- height: Int,
- flags: Int,
- taskId: Int
- ): LayoutParams {
- return LayoutParams(
- width, height, x, y,
- LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
- flags,
- PixelFormat.TRANSPARENT
- ).apply {
- title = "Additional view container of Task=$taskId"
- gravity = Gravity.LEFT or Gravity.TOP
- setTrustedOverlay()
- }
- }
- }
}
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 ff418c6daa02..e43c3a613157 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
@@ -21,10 +21,13 @@ import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.Rect
import android.util.SparseArray
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
import androidx.core.util.valueIterator
import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
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.DesktopRepository
@@ -45,10 +48,16 @@ class DesktopTilingDecorViewModel(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val taskRepository: DesktopRepository,
-) {
+) : DisplayChangeController.OnDisplayChangingListener {
@VisibleForTesting
var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
+ init {
+ // TODO(b/374309287): Move this interface implementation to
+ // [DesktopModeWindowDecorViewModel] when the migration is done.
+ displayController.addDisplayChangingController(this)
+ }
+
fun snapToHalfScreen(
taskInfo: ActivityManager.RunningTaskInfo,
desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -102,7 +111,20 @@ class DesktopTilingDecorViewModel(
fun onUserChange() {
for (tilingHandler in tilingTransitionHandlerByDisplayId.valueIterator()) {
- tilingHandler.onUserChange()
+ tilingHandler.resetTilingSession()
}
}
+
+ override fun onDisplayChange(
+ displayId: Int,
+ fromRotation: Int,
+ toRotation: Int,
+ newDisplayAreaInfo: DisplayAreaInfo?,
+ t: WindowContainerTransaction?,
+ ) {
+ // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+ // [toRotation] can be one of the [@Surface.Rotation] values.
+ if ((fromRotation % 2 == toRotation % 2)) return
+ tilingTransitionHandlerByDisplayId.get(displayId)?.resetTilingSession()
+ }
}
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 9bf1304f2b39..209eb5e501b2 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.RoundedCorner
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
@@ -53,12 +54,14 @@ class DesktopTilingDividerWindowManager(
private val transitionHandler: DesktopTilingWindowDecoration,
private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
private var dividerBounds: Rect,
+ private val displayContext: Context,
) : WindowlessWindowManager(config, leash, null), DividerMoveCallback, View.OnLayoutChangeListener {
private lateinit var viewHost: SurfaceControlViewHost
private var tilingDividerView: TilingDividerView? = null
private var dividerShown = false
private var handleRegionWidth: Int = -1
private var setTouchRegion = true
+ private val maxRoundedCornerRadius = getMaxRoundedCornerRadius()
/**
* Gets bounds of divider window with screen based coordinate on the param Rect.
@@ -93,7 +96,11 @@ class DesktopTilingDividerWindowManager(
getDividerBounds(tmpDividerBounds)
dividerView.setup(this, tmpDividerBounds)
t.setRelativeLayer(leash, relativeLeash, 1)
- .setPosition(leash, dividerBounds.left.toFloat(), dividerBounds.top.toFloat())
+ .setPosition(
+ leash,
+ dividerBounds.left.toFloat() - maxRoundedCornerRadius,
+ dividerBounds.top.toFloat(),
+ )
.show(leash)
syncQueue.runInSync { transaction ->
transaction.merge(t)
@@ -144,7 +151,7 @@ class DesktopTilingDividerWindowManager(
*/
override fun onDividerMove(pos: Int): Boolean {
val t = transactionSupplier.get()
- t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+ t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
val dividerWidth = dividerBounds.width()
dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
return transitionHandler.onDividerHandleMoved(dividerBounds, t)
@@ -157,7 +164,7 @@ class DesktopTilingDividerWindowManager(
override fun onDividerMovedEnd(pos: Int) {
setSlippery(true)
val t = transactionSupplier.get()
- t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+ 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)
@@ -166,7 +173,7 @@ class DesktopTilingDividerWindowManager(
private fun getWindowManagerParams(): WindowManager.LayoutParams {
val lp =
WindowManager.LayoutParams(
- dividerBounds.width(),
+ dividerBounds.width() + 2 * maxRoundedCornerRadius,
dividerBounds.height(),
TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE or
@@ -225,4 +232,15 @@ class DesktopTilingDividerWindowManager(
}
viewHost.relayout(lp)
}
+
+ private fun getMaxRoundedCornerRadius(): Int {
+ val display = displayContext.display
+ return listOf(
+ RoundedCorner.POSITION_TOP_LEFT,
+ RoundedCorner.POSITION_TOP_RIGHT,
+ RoundedCorner.POSITION_BOTTOM_RIGHT,
+ RoundedCorner.POSITION_BOTTOM_LEFT,
+ )
+ .maxOf { position -> display.getRoundedCorner(position)?.getRadius() ?: 0 }
+ }
}
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 6ea1d14cc734..c46767c3a51d 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
@@ -23,10 +23,12 @@ import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.IBinder
+import android.os.UserHandle
import android.util.Slog
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
@@ -125,7 +127,6 @@ class DesktopTilingWindowDecoration(
resizeMetadata.getLeash(),
startBounds = currentBounds,
endBounds = destinationBounds,
- isResizable = taskInfo.isResizeable,
)
}
}
@@ -194,6 +195,7 @@ class DesktopTilingWindowDecoration(
val builder = SurfaceControl.Builder()
rootTdaOrganizer.attachToDisplayArea(displayId, builder)
val leash = builder.setName(TILING_DIVIDER_TAG).setContainerLayer().build()
+ val displayContext = displayController.getDisplayContext(displayId) ?: return null
val tilingManager =
displayLayout?.let {
dividerBounds = inflateDividerBounds(it)
@@ -206,6 +208,7 @@ class DesktopTilingWindowDecoration(
this,
transactionSupplier,
dividerBounds,
+ displayContext,
)
}
// a leash to present the divider on top of, without re-parenting.
@@ -342,7 +345,9 @@ class DesktopTilingWindowDecoration(
private fun isMinimized(changeMode: Int, infoType: Int): Boolean {
return (changeMode == TRANSIT_TO_BACK &&
- (infoType == TRANSIT_MINIMIZE || infoType == TRANSIT_TO_BACK))
+ (infoType == TRANSIT_MINIMIZE ||
+ infoType == TRANSIT_TO_BACK ||
+ infoType == TRANSIT_OPEN))
}
class AppResizingHelper(
@@ -358,6 +363,8 @@ class DesktopTilingWindowDecoration(
private lateinit var resizeVeilBitmap: Bitmap
private lateinit var resizeVeil: ResizeVeil
private val displayContext = displayController.getDisplayContext(taskInfo.displayId)
+ private val userContext =
+ context.createContextAsUser(UserHandle.of(taskInfo.userId), /* flags= */ 0)
fun initIfNeeded() {
if (!isInitialised) {
@@ -376,7 +383,7 @@ class DesktopTilingWindowDecoration(
displayContext?.let {
createIconFactory(displayContext, R.dimen.desktop_mode_resize_veil_icon_size)
} ?: return
- val pm = context.getApplicationContext().getPackageManager()
+ val pm = userContext.getPackageManager()
val activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */)
val provider = IconProvider(displayContext)
val appIconDrawable = provider.getIcon(activityInfo)
@@ -478,7 +485,7 @@ class DesktopTilingWindowDecoration(
}
}
- fun onUserChange() {
+ fun resetTilingSession() {
if (leftTaskResizingHelper != null) {
removeTask(leftTaskResizingHelper, taskVanished = false, shouldDelayUpdate = true)
leftTaskResizingHelper = null
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 065a5d77ccd3..89229051941c 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
@@ -49,11 +49,10 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
var handleRegionWidth: Int = 0
private var handleRegionHeight = 0
private var lastAcceptedPos = 0
- @VisibleForTesting
- var handleStartY = 0
- @VisibleForTesting
- var handleEndY = 0
+ @VisibleForTesting var handleStartY = 0
+ @VisibleForTesting var handleEndY = 0
private var canResize = false
+ private var resized = false
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
* insets.
@@ -119,7 +118,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
val dividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width)
val backgroundLeft = (width - dividerSize) / 2
val backgroundTop = 0
- val backgroundRight = left + dividerSize
+ val backgroundRight = backgroundLeft + dividerSize
val backgroundBottom = height
backgroundRect.set(backgroundLeft, backgroundTop, backgroundRight, backgroundBottom)
}
@@ -209,7 +208,6 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
if (!isWithinHandleRegion(yTouchPosInDivider)) return true
callback.onDividerMoveStart(touchPos)
setTouching()
- startPos = touchPos
canResize = true
}
@@ -223,19 +221,20 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
val pos = dividerBounds.left + touchPos - startPos
if (callback.onDividerMove(pos)) {
lastAcceptedPos = touchPos
+ resized = true
}
}
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP -> {
if (!canResize) return true
- dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
- if (moving) {
+ if (moving && resized) {
+ dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
callback.onDividerMovedEnd(dividerBounds.left)
- moving = false
- canResize = false
}
-
+ moving = false
+ canResize = false
+ resized = false
releaseTouching()
}
}
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 b43a9839f042..b5700ffb046b 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
@@ -36,10 +36,13 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
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).
@@ -66,12 +69,10 @@ internal class AppHandleViewHolder(
) : Data()
private lateinit var taskInfo: RunningTaskInfo
- private val position: Point = Point()
- private var width: Int = 0
- private var height: Int = 0
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
private val inputManager = context.getSystemService(InputManager::class.java)
+ private var statusBarInputLayerExists = false
// An invisible View that takes up the same coordinates as captionHandle but is layered
// above the status bar. The purpose of this View is to receive input intended for
@@ -111,54 +112,21 @@ internal class AppHandleViewHolder(
) {
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
this.taskInfo = taskInfo
- this.position.set(position)
- this.width = width
- this.height = height
- if (!isCaptionVisible && statusBarInputLayer != null) {
- detachStatusBarInputLayer()
+ // If handle is not in status bar region(i.e., bottom stage in vertical split),
+ // do not create an input layer
+ if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
+ if (!isCaptionVisible && statusBarInputLayerExists) {
+ disposeStatusBarInputLayer()
return
}
- }
-
- fun bindStatusBarInputLayer(
- statusBarLayer: AdditionalSystemViewContainer
- ) {
- // Input layer view modification takes a significant amount of time;
+ // Input layer view creation / modification takes a significant amount of time;
// post them so we don't hold up DesktopModeWindowDecoration#relayout.
- if (statusBarLayer == statusBarInputLayer) {
+ if (statusBarInputLayerExists) {
handler.post { updateStatusBarInputLayer(position) }
- return
- }
- // Remove the old input layer when changing to a new one.
- if (statusBarInputLayer != null) detachStatusBarInputLayer()
- if (statusBarLayer.view.visibility == View.INVISIBLE) {
- statusBarLayer.view.visibility = View.VISIBLE
- }
- statusBarInputLayer = statusBarLayer
- statusBarInputLayer?.let {
- inputLayer -> setupAppHandleA11y(inputLayer.view)
- }
- handler.post {
- val view = statusBarInputLayer?.view
- ?: error("Unable to find statusBarInputLayer View")
- // Caption handle is located within the status bar region, meaning the
- // DisplayPolicy will attempt to transfer this input to status bar if it's
- // a swipe down. Pilfer here to keep the gesture in handle alone.
- view.setOnTouchListener { v, event ->
- if (event.actionMasked == ACTION_DOWN) {
- inputManager.pilferPointers(v.viewRootImpl.inputToken)
- }
- captionHandle.dispatchTouchEvent(event)
- return@setOnTouchListener true
- }
- view.setOnHoverListener { _, event ->
- captionHandle.onHoverEvent(event)
- }
- val lp = statusBarInputLayer?.view?.layoutParams as WindowManager.LayoutParams
- lp.x = position.x
- lp.y = position.y
- lp.width = width
- lp.height = height
+ } else {
+ // Input layer is created on a delay; prevent multiple from being created.
+ statusBarInputLayerExists = true
+ handler.post { createStatusBarInputLayer(position, width, height) }
}
}
@@ -170,6 +138,40 @@ internal class AppHandleViewHolder(
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
+ private fun createStatusBarInputLayer(handlePosition: Point,
+ handleWidth: Int,
+ handleHeight: Int) {
+ if (!Flags.enableHandleInputFix()) return
+ statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
+ taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ )
+ val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
+ val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
+ "LayoutParams")
+ lp.title = "Handle Input Layer of task " + taskInfo.taskId
+ lp.setTrustedOverlay()
+ // Make this window a spy window to enable it to pilfer pointers from the system-wide
+ // gesture listener that receives events before window. This is to prevent notification
+ // shade gesture when we swipe down to enter desktop.
+ lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ view.setOnHoverListener { _, event ->
+ captionHandle.onHoverEvent(event)
+ }
+ // Caption handle is located within the status bar region, meaning the
+ // DisplayPolicy will attempt to transfer this input to status bar if it's
+ // a swipe down. Pilfer here to keep the gesture in handle alone.
+ view.setOnTouchListener { v, event ->
+ if (event.actionMasked == ACTION_DOWN) {
+ inputManager.pilferPointers(v.viewRootImpl.inputToken)
+ }
+ captionHandle.dispatchTouchEvent(event)
+ return@setOnTouchListener true
+ }
+ setupAppHandleA11y(view)
+ windowManagerWrapper.updateViewLayout(view, lp)
+ }
+
private fun setupAppHandleA11y(view: View) {
view.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
@@ -222,12 +224,15 @@ internal class AppHandleViewHolder(
}
/**
- * Remove the input listeners from the input layer and remove it from this view holder.
+ * Remove the input layer from [WindowManager]. Should be used when caption handle
+ * is not visible.
*/
- fun detachStatusBarInputLayer() {
- statusBarInputLayer?.view?.setOnTouchListener(null)
- statusBarInputLayer?.view?.setOnHoverListener(null)
- statusBarInputLayer = null
+ fun disposeStatusBarInputLayer() {
+ statusBarInputLayerExists = false
+ handler.post {
+ statusBarInputLayer?.releaseView()
+ statusBarInputLayer = null
+ }
}
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 72d4dc6ffac9..13a8518ae8ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -700,7 +700,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
eq(tInfo), eq(st), eq(ft), eq(callback));
mBackTransitionHandler.onAnimationFinished();
- final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE,
+ final TransitionInfo.Change openToClose = createAppChangeFromChange(open, TRANSIT_CLOSE,
FLAG_BACK_GESTURE_ANIMATED);
tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
@@ -830,6 +830,16 @@ public class BackAnimationControllerTest extends ShellTestCase {
return change;
}
+ private TransitionInfo.Change createAppChangeFromChange(
+ TransitionInfo.Change originalChange, @TransitionInfo.TransitionMode int mode,
+ @TransitionInfo.ChangeFlags int flags) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ originalChange.getTaskInfo().token, originalChange.getLeash());
+ change.setMode(mode);
+ change.setFlags(flags);
+ return change;
+ }
+
private static TransitionInfo createTransitionInfo(
@WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
final TransitionInfo info = new TransitionInfo(type, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4ac066e4ffb0..1f2eaa6757e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -99,10 +99,11 @@ class BubbleViewInfoTest : ShellTestCase() {
val shellController = ShellController(context, shellInit, shellCommandHandler,
mock<DisplayInsetsController>(), mainExecutor)
bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleLogger = mock<BubbleLogger>()
val bubbleData =
BubbleData(
context,
- mock<BubbleLogger>(),
+ bubbleLogger,
bubblePositioner,
BubbleEducationController(context),
mainExecutor,
@@ -125,7 +126,7 @@ class BubbleViewInfoTest : ShellTestCase() {
WindowManagerShellWrapper(mainExecutor),
mock<UserManager>(),
mock<LauncherApps>(),
- mock<BubbleLogger>(),
+ bubbleLogger,
mock<TaskStackListenerImpl>(),
ShellTaskOrganizer(mainExecutor),
bubblePositioner,
@@ -154,7 +155,7 @@ class BubbleViewInfoTest : ShellTestCase() {
bubbleController,
mainExecutor
)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index c52d9dd24165..dc0f213338be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -190,7 +190,7 @@ public class SplitLayoutTests extends ShellTestCase {
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(),
+ verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(), any(),
mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 3e9c732b9c3b..aabd973fce90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -43,6 +43,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreef
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -50,10 +51,10 @@ import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import kotlin.test.assertNotNull
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
@@ -94,6 +95,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var taskStackListener: TaskStackListenerImpl
@Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
@@ -116,7 +118,13 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
taskRepository =
- DesktopRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ testScope
+ )
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
@@ -172,8 +180,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT
task.topActivityInfo = activityInfo
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- taskRepository.addActiveTask(DEFAULT_DISPLAY, task.taskId)
- taskRepository.updateTaskVisibility(DEFAULT_DISPLAY, task.taskId, visible = true)
+ taskRepository.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
runningTasks.add(task)
taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
@@ -196,7 +203,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
val task = setUpFreeformTask(isResizeable = false)
- taskRepository.updateTaskVisibility(task.displayId, task.taskId, visible = false)
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
SCREEN_ORIENTATION_LANDSCAPE)
@@ -261,9 +268,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
task.topActivityInfo = activityInfo
task.isResizeable = isResizeable
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- taskRepository.addActiveTask(displayId, task.taskId)
- taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
- taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
+ taskRepository.addTask(displayId, task.taskId, isVisible = true)
runningTasks.add(task)
return task
}
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 e83f5c7a79a1..e05a0b54fcf4 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
@@ -81,7 +81,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
@Before
fun setUp() {
desktopRepository = DesktopRepository(
- context, ShellInit(TestShellExecutor()), mock(), mock()
+ context, ShellInit(TestShellExecutor()), mock(), mock(), mock()
)
whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
.thenReturn(mockDisplayLayout)
@@ -347,7 +347,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
wct = wct,
displayId = DEFAULT_DISPLAY,
excludeTaskId = task.taskId
- )?.invoke(transition)
+ ).asExit()?.runOnTransitionStart?.invoke(transition)
assertThat(controller.pendingExternalExitTransitions.any { exit ->
exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
@@ -402,7 +402,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
immersive = true
)
- controller.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
+ controller.exitImmersiveIfApplicable(wct, task)
+ .asExit()?.runOnTransitionStart?.invoke(transition)
assertThat(controller.pendingExternalExitTransitions.any { exit ->
exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
@@ -412,23 +413,36 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotAddPendingExitOnRun() {
+ fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotExit() {
val task = createFreeformTask()
whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
val wct = WindowContainerTransaction()
- val transition = Binder()
desktopRepository.setTaskInFullImmersiveState(
- displayId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
taskId = task.taskId,
immersive = false
)
- controller.exitImmersiveIfApplicable(wct, task)?.invoke(transition)
+ val result = controller.exitImmersiveIfApplicable(wct, task)
- assertThat(controller.pendingExternalExitTransitions.any { exit ->
- exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
- && exit.taskId == task.taskId
- }).isFalse()
+ assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byDisplay_notInImmersive_doesNotExit() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ val result = controller.exitImmersiveIfApplicable(wct, task.displayId)
+
+ assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit)
}
@Test
@@ -631,7 +645,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
taskId = task.taskId,
immersive = true
)
- controller.exitImmersiveIfApplicable(wct, task)?.invoke(Binder())
+ controller.exitImmersiveIfApplicable(wct, task)
+ .asExit()?.runOnTransitionStart?.invoke(Binder())
controller.moveTaskToNonImmersive(task)
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 be0663cbd70d..df061e368071 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
@@ -31,16 +31,22 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TransitionType
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMixedTransition
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -48,9 +54,13 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -71,9 +81,12 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@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
private lateinit var mixedHandler: DesktopMixedTransitionHandler
@@ -86,8 +99,11 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
desktopRepository,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
+ desktopImmersiveController,
interactionJankMonitor,
- mockHandler
+ mockHandler,
+ shellInit,
+ rootTaskDisplayAreaOrganizer,
)
}
@@ -146,7 +162,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val transition = mock<IBinder>()
val transitionInfo =
createTransitionInfo(
- changeMode = WindowManager.TRANSIT_OPEN,
+ changeMode = TRANSIT_OPEN,
task = createTask(WINDOWING_MODE_FREEFORM)
)
whenever(freeformTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any()))
@@ -164,7 +180,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
+ val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
@@ -172,6 +190,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
)
.thenReturn(true)
+ whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
+ .thenReturn(transition)
+ mixedHandler.startRemoveTransition(wct)
val started = mixedHandler.startAnimation(
transition = transition,
@@ -187,12 +208,17 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
+ val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(1)
whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
.thenReturn(mock())
+ whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
+ .thenReturn(transition)
+ mixedHandler.startRemoveTransition(wct)
mixedHandler.startAnimation(
transition = transition,
@@ -220,6 +246,324 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
}
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
+ val wct = WindowContainerTransaction()
+ val task = createTask(WINDOWING_MODE_FREEFORM)
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(Binder())
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = task.taskId,
+ exitingImmersiveTask = null
+ )
+
+ verify(transitions).startTransition(TRANSIT_OPEN, wct, /* handler= */ null)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startLaunchTransition_immersiveMixEnabled_usesMixedHandler() {
+ val wct = WindowContainerTransaction()
+ val task = createTask(WINDOWING_MODE_FREEFORM)
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(Binder())
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = task.taskId,
+ exitingImmersiveTask = null
+ )
+
+ verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
+ val wct = WindowContainerTransaction()
+ val task = createTask(WINDOWING_MODE_FREEFORM)
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(Binder())
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = task.taskId,
+ exitingImmersiveTask = null
+ )
+
+ verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAndAnimateLaunchTransition_withoutImmersiveChange_dispatchesAllChangesToLeftOver() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ exitingImmersiveTask = null,
+ )
+ val launchTaskChange = createChange(launchingTask)
+ val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange, otherChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(transitions).dispatchTransition(
+ eq(transition),
+ argThat { info ->
+ info.changes.contains(launchTaskChange) && info.changes.contains(otherChange)
+ },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAndAnimateLaunchTransition_withImmersiveChange_mixesAnimations() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val immersiveTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ exitingImmersiveTask = immersiveTask.taskId,
+ )
+ val launchTaskChange = createChange(launchingTask)
+ val immersiveChange = createChange(immersiveTask)
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange, immersiveChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(desktopImmersiveController)
+ .animateResizeChange(eq(immersiveChange), any(), any(), any())
+ verify(transitions).dispatchTransition(
+ eq(transition),
+ argThat { info ->
+ info.changes.contains(launchTaskChange) && !info.changes.contains(immersiveChange)
+ },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val launchTaskChange = createChange(launchingTask)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ minimizingTaskId = null,
+ )
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(rootTaskDisplayAreaOrganizer, times(0))
+ .reparentToDisplayArea(anyInt(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val launchTaskChange = createChange(launchingTask)
+ val minimizeChange = createChange(minimizingTask)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ minimizingTaskId = minimizingTask.taskId,
+ )
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange, minimizeChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+ anyInt(), eq(minimizeChange.leash), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val launchTaskChange = createChange(launchingTask)
+ 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,
+ )
+ )
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(rootTaskDisplayAreaOrganizer, times(0))
+ .reparentToDisplayArea(anyInt(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val launchTaskChange = createChange(launchingTask)
+ val minimizeChange = createChange(minimizingTask)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Launch(
+ transition = transition,
+ launchingTask = launchingTask.taskId,
+ minimizingTask = minimizingTask.taskId,
+ exitingImmersiveTask = null,
+ )
+ )
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange, minimizeChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+ anyInt(), eq(minimizeChange.leash), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAndAnimateLaunchTransition_removesPendingMixedTransition() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ exitingImmersiveTask = null,
+ )
+ val launchTaskChange = createChange(launchingTask)
+ mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(launchTaskChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAndAnimateLaunchTransition_aborted_removesPendingMixedTransition() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+
+ mixedHandler.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ taskId = launchingTask.taskId,
+ exitingImmersiveTask = null,
+ )
+ mixedHandler.onTransitionConsumed(
+ transition = transition,
+ aborted = true,
+ finishTransaction = SurfaceControl.Transaction()
+ )
+
+ assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
+ }
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_CLOSE,
changeMode: Int = WindowManager.TRANSIT_CLOSE,
@@ -235,6 +579,18 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
}
+ private fun createTransitionInfo(
+ @TransitionType type: Int,
+ changes: List<TransitionInfo.Change> = emptyList()
+ ): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply {
+ changes.forEach { change -> addChange(change) }
+ }
+
+ private fun createChange(task: RunningTaskInfo): TransitionInfo.Change =
+ TransitionInfo.Change(task.token, SurfaceControl()).apply {
+ taskInfo = task
+ }
+
private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
TestRunningTaskInfoBuilder()
.setActivityType(ACTIVITY_TYPE_STANDARD)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index f25faa53f356..7c4ce4acfc9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -59,6 +59,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Before
@@ -522,6 +523,23 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
}
@Test
+ fun transitMinimize_logExitReasongMinimized() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // minimize the task
+ val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ assertFalse(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
// add a freeform task to an existing session
val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
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 d90443c99d37..414c1a658b95 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode
import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.util.ArraySet
import android.view.Display.DEFAULT_DISPLAY
@@ -29,6 +30,7 @@ import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
@@ -43,6 +45,7 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -50,6 +53,7 @@ import org.mockito.Mockito.inOrder
import org.mockito.Mockito.spy
import org.mockito.kotlin.any
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -58,12 +62,15 @@ import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
class DesktopRepositoryTest : ShellTestCase() {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
@Mock private lateinit var testExecutor: ShellExecutor
@Mock private lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Before
fun setUp() {
@@ -71,7 +78,14 @@ class DesktopRepositoryTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
+ repo =
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ datastoreScope
+ )
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
)
@@ -84,65 +98,65 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun addActiveTask_notifiesListener() {
+ fun addTask_notifiesActiveTaskListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
- fun addActiveTask_taskIsActive() {
+ fun addTask_marksTaskActive() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.isActiveTask(1)).isTrue()
}
@Test
- fun addSameActiveTaskTwice_notifiesOnce() {
+ fun addSameTaskTwice_notifiesOnce() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
- fun addActiveTask_multipleTasksAdded_notifiesForAllTasks() {
+ fun addTask_multipleTasksAdded_notifiesForAllTasks() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
- fun addActiveTask_multipleDisplays_notifiesCorrectListener() {
+ fun addTask_multipleDisplays_notifiesCorrectListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
- repo.addActiveTask(SECOND_DISPLAY, taskId = 3)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.addTask(SECOND_DISPLAY, taskId = 3, isVisible = true)
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(1)
}
@Test
- fun removeActiveTask_notifiesListener() {
+ fun removeActiveTask_notifiesActiveTaskListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeActiveTask(1)
@@ -151,10 +165,10 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun removeActiveTask_taskNotActive() {
+ fun removeActiveTask_marksTaskNotActive() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeActiveTask(1)
@@ -175,7 +189,7 @@ class DesktopRepositoryTest : ShellTestCase() {
fun remoteActiveTask_listenerForOtherDisplayNotNotified() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeActiveTask(1)
@@ -201,8 +215,8 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun updateTaskVisibility_singleVisibleNonClosingTask_updatesTasksCorrectly() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ fun updateTask_singleVisibleNonClosingTask_updatesTasksCorrectly() {
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.isVisibleTask(1)).isTrue()
assertThat(repo.isClosingTask(1)).isFalse()
@@ -214,8 +228,35 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun updateTaskVisibility_multipleTasks_persistsVisibleTasks() =
+ runTest(StandardTestDispatcher()) {
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+
+ inOrder(persistentRepository).run {
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(arrayOf(1)),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf()
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(arrayOf(1, 2)),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf()
+ )
+ }
+ }
+
+ @Test
fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.addClosingTask(DEFAULT_DISPLAY, 1)
// A visible task that's closing
@@ -229,13 +270,14 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.minimizeTask(DEFAULT_DISPLAY, 1)
+ val taskId = 1
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
+ repo.minimizeTask(DEFAULT_DISPLAY, taskId)
// The visible task that's closing
- assertThat(repo.isVisibleTask(1)).isTrue()
- assertThat(repo.isMinimizedTask(1)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ assertThat(repo.isVisibleTask(taskId)).isFalse()
+ assertThat(repo.isMinimizedTask(taskId)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(taskId)).isFalse()
// Not a visible task
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
@@ -243,17 +285,19 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun isOnlyVisibleNonClosingTask_multipleVisibleNonClosingTasks() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
// Not the only task
assertThat(repo.isVisibleTask(1)).isTrue()
assertThat(repo.isClosingTask(1)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+
// Not the only task
assertThat(repo.isVisibleTask(2)).isTrue()
assertThat(repo.isClosingTask(2)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse()
+
// Not a visible task
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isClosingTask(99)).isFalse()
@@ -262,9 +306,9 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun isOnlyVisibleNonClosingTask_multipleDisplays() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
- repo.updateTaskVisibility(SECOND_DISPLAY, taskId = 3, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.updateTask(SECOND_DISPLAY, taskId = 3, isVisible = true)
// Not the only task on DEFAULT_DISPLAY
assertThat(repo.isVisibleTask(1)).isTrue()
@@ -282,7 +326,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addVisibleTasksListener_notifiesVisibleFreeformTask() {
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
@@ -295,7 +339,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
- repo.updateTaskVisibility(SECOND_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
@@ -307,13 +351,13 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun updateTaskVisibility_addVisibleTasksNotifiesListener() {
+ fun updateTask_visible_addVisibleTasksNotifiesListener() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
@@ -321,12 +365,12 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun updateTaskVisibility_addVisibleTaskNotifiesListenerForThatDisplay() {
+ fun updateTask_visibleTask_addVisibleTaskNotifiesListenerForThatDisplay() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
@@ -334,7 +378,7 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
- repo.updateTaskVisibility(displayId = 1, taskId = 2, visible = true)
+ repo.updateTask(displayId = 1, taskId = 2, isVisible = true)
executor.flushAll()
// Listener for secondary display is notified
@@ -345,17 +389,17 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun updateTaskVisibility_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
+ fun updateTask_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
// Mark task 1 visible on secondary display
- repo.updateTaskVisibility(displayId = 1, taskId = 1, visible = true)
+ repo.updateTask(displayId = 1, taskId = 1, isVisible = true)
executor.flushAll()
// Default display should have 2 calls
@@ -370,22 +414,22 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun updateTaskVisibility_removeVisibleTasksNotifiesListener() {
+ fun updateTask_removeVisibleTasksNotifiesListener() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = false)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
@@ -397,17 +441,17 @@ class DesktopRepositoryTest : ShellTestCase() {
* This tests that task is removed from the last parent display when it vanishes.
*/
@Test
- fun updateTaskVisibility_removeVisibleTasksRemovesTaskWithInvalidDisplay() {
+ fun updateTask_removeVisibleTasksRemovesTaskWithInvalidDisplay() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
- repo.updateTaskVisibility(INVALID_DISPLAY, taskId = 1, visible = false)
+ repo.updateTask(INVALID_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
@@ -420,30 +464,30 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 2, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = false)
assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 999, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 999, isVisible = false)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@@ -453,42 +497,42 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
- repo.updateTaskVisibility(SECOND_DISPLAY, taskId = 2, visible = true)
+ repo.updateTask(SECOND_DISPLAY, taskId = 2, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
- repo.updateTaskVisibility(SECOND_DISPLAY, taskId = 1, visible = true)
+ repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
- repo.updateTaskVisibility(DEFAULT_DISPLAY, taskId = 1, visible = false)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
- repo.updateTaskVisibility(SECOND_DISPLAY, taskId = 1, visible = false)
+ repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = false)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
- fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ fun addTask_didNotExist_addsToTop() {
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks.size).isEqualTo(3)
@@ -499,11 +543,11 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
- fun addOrMoveFreeformTaskToTop_noTaskExists_persistenceEnabled_addsToTop() =
+ fun addTask_noTaskExists_persistenceEnabled_addsToTop() =
runTest(StandardTestDispatcher()) {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks).containsExactly(7, 6, 5).inOrder()
@@ -520,7 +564,7 @@ class DesktopRepositoryTest : ShellTestCase() {
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
- visibleTasks = ArraySet(),
+ visibleTasks = ArraySet(arrayOf(5)),
minimizedTasks = ArraySet(),
freeformTasksInZOrder = arrayListOf(6, 5)
)
@@ -528,7 +572,7 @@ class DesktopRepositoryTest : ShellTestCase() {
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
- visibleTasks = ArraySet(),
+ visibleTasks = ArraySet(arrayOf(5, 6)),
minimizedTasks = ArraySet(),
freeformTasksInZOrder = arrayListOf(7, 6, 5)
)
@@ -536,12 +580,12 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ fun addTask_alreadyExists_movesToTop() {
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks.size).isEqualTo(3)
@@ -549,10 +593,10 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun addOrMoveFreeformTaskToTop_taskIsMinimized_unminimizesTask() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ fun addTask_taskIsMinimized_unminimizesTask() {
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
repo.minimizeTask(displayId = 0, taskId = 6)
val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
@@ -564,9 +608,9 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun minimizeTask_persistenceEnabled_taskIsPersistedAsMinimized() =
runTest(StandardTestDispatcher()) {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
repo.minimizeTask(displayId = 0, taskId = 6)
@@ -586,7 +630,7 @@ class DesktopRepositoryTest : ShellTestCase() {
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
- visibleTasks = ArraySet(),
+ visibleTasks = ArraySet(arrayOf(5)),
minimizedTasks = ArraySet(),
freeformTasksInZOrder = arrayListOf(6, 5)
)
@@ -594,15 +638,15 @@ class DesktopRepositoryTest : ShellTestCase() {
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
- visibleTasks = ArraySet(),
+ visibleTasks = ArraySet(arrayOf(5, 6)),
minimizedTasks = ArraySet(),
freeformTasksInZOrder = arrayListOf(7, 6, 5)
)
- verify(persistentRepository)
+ verify(persistentRepository, times(2))
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
- visibleTasks = ArraySet(),
+ visibleTasks = ArraySet(arrayOf(5, 7)),
minimizedTasks = ArraySet(arrayOf(6)),
freeformTasksInZOrder = arrayListOf(7, 6, 5)
)
@@ -610,10 +654,10 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
- fun addOrMoveFreeformTaskToTop_taskIsUnminimized_noop() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+ fun addTask_taskIsUnminimized_noop() {
+ repo.addTask(DEFAULT_DISPLAY, 5, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 6, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, 7, isVisible = true)
val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(tasks).containsExactly(7, 6, 5).inOrder()
@@ -622,7 +666,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_invalidDisplay_removesTaskFromFreeformTasks() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(INVALID_DISPLAY, taskId = 1)
@@ -636,7 +680,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeFreeformTask_invalidDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
runTest(StandardTestDispatcher()) {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(INVALID_DISPLAY, taskId = 1)
@@ -661,7 +705,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_validDisplay_removesTaskFromFreeformTasks() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(DEFAULT_DISPLAY, taskId = 1)
@@ -673,7 +717,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeFreeformTask_validDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
runTest(StandardTestDispatcher()) {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(DEFAULT_DISPLAY, taskId = 1)
@@ -698,7 +742,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_validDisplay_differentDisplay_doesNotRemovesTask() {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(SECOND_DISPLAY, taskId = 1)
@@ -710,7 +754,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeFreeformTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() {
runTest(StandardTestDispatcher()) {
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeFreeformTask(SECOND_DISPLAY, taskId = 1)
@@ -736,8 +780,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
- repo.addActiveTask(THIRD_DISPLAY, taskId)
- repo.addOrMoveFreeformTaskToTop(THIRD_DISPLAY, taskId)
+ repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
@@ -748,8 +791,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_removesTaskBoundsBeforeImmersive() {
val taskId = 1
- repo.addActiveTask(THIRD_DISPLAY, taskId)
- repo.addOrMoveFreeformTaskToTop(THIRD_DISPLAY, taskId)
+ repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeFullImmersive(taskId, Rect(0, 0, 200, 200))
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
@@ -762,8 +804,7 @@ class DesktopRepositoryTest : ShellTestCase() {
val taskId = 1
val listener = TestListener()
repo.addActiveTaskListener(listener)
- repo.addActiveTask(DEFAULT_DISPLAY, taskId)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
@@ -774,8 +815,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_unminimizesTask() {
val taskId = 1
- repo.addActiveTask(DEFAULT_DISPLAY, taskId)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.minimizeTask(DEFAULT_DISPLAY, taskId)
repo.removeFreeformTask(DEFAULT_DISPLAY, taskId)
@@ -786,8 +826,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeFreeformTask_updatesTaskVisibility() {
val taskId = 1
- repo.addActiveTask(DEFAULT_DISPLAY, taskId)
- repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
@@ -855,8 +894,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun minimizeTask_withInvalidDisplay_minimizesCorrectTask() {
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 0)
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 0)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 0, isVisible = true)
repo.minimizeTask(displayId = INVALID_DISPLAY, taskId = 0)
@@ -888,9 +926,9 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
- fun updateTaskVisibility_minimizedTaskBecomesVisible_unminimizesTask() {
+ fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() {
repo.minimizeTask(displayId = 10, taskId = 2)
- repo.updateTaskVisibility(displayId = 10, taskId = 2, visible = true)
+ repo.updateTask(displayId = 10, taskId = 2, isVisible = true)
val isMinimizedTask = repo.isMinimizedTask(taskId = 2)
@@ -899,13 +937,9 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() {
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
- repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 2)
- repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 1)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
val tasks = repo.getExpandedTasksOrdered(displayId = 0)
@@ -914,13 +948,9 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getExpandedTasksOrdered_excludesMinimizedTasks() {
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2)
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
val tasks = repo.getExpandedTasksOrdered(displayId = DEFAULT_DISPLAY)
@@ -976,13 +1006,10 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeDesktop_multipleTasks_removesAll() {
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2)
- repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1)
+ // The front-most task will be the one added last through `addTask`.
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
new file mode 100644
index 000000000000..e977966a45fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for {@link DesktopTaskChangeListener}
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopTaskChangeListenerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopTaskChangeListenerTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
+
+ private val desktopRepository = mock<DesktopRepository>()
+
+ @Before
+ fun setUp() {
+ desktopTaskChangeListener = DesktopTaskChangeListener(desktopRepository)
+ }
+
+ @Test
+ fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopRepository, never()).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
+ val task = createFreeformTask().apply { isVisible = false }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
+ val task = createFreeformTask().apply { isVisible = false }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskMovingToFront(task)
+
+ verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopRepository).updateTask(task.displayId, task.taskId, isVisible = false)
+ verify(desktopRepository).minimizeTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopRepository, never()).minimizeTask(task.displayId, task.taskId)
+ verify(desktopRepository).removeClosingTask(task.taskId)
+ verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopRepository).removeClosingTask(task.taskId)
+ verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ }
+}
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 0a6dfbf4c828..b157d557c1d8 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
@@ -110,6 +110,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplit
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.recents.RecentTasksController
@@ -196,6 +197,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
+ @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler
@@ -223,6 +225,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock private lateinit var mockInputManager: InputManager
@Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
@Mock lateinit var motionEvent: MotionEvent
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
@Mock
@@ -263,7 +266,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
+ taskRepository =
+ DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -288,6 +292,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(mMockDesktopImmersiveController
+ .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>()))
+ .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
+ whenever(mMockDesktopImmersiveController
+ .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
+ .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -323,6 +333,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
transitions,
keyguardManager,
mReturnToDragStartAnimator,
+ desktopMixedTransitionHandler,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
dragAndDropTransitionHandler,
@@ -1389,7 +1400,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.moveTaskToFront(task1, remoteTransition = null)
- val wct = getLatestWct(type = TRANSIT_TO_FRONT)
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, task1)
}
@@ -1398,10 +1409,17 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
setUpHomeTask()
val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ whenever(desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )).thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
- val wct = getLatestWct(type = TRANSIT_TO_FRONT)
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
wct.assertReorderAt(0, freeformTasks[0], toTop = true)
wct.assertReorderAt(1, freeformTasks[1], toTop = false)
@@ -1443,7 +1461,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.moveTaskToFront(task.taskId, remoteTransition = null)
- val wct = getLatestWct(type = TRANSIT_OPEN)
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
}
@@ -1453,10 +1471,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val task = createTaskInfo(1001)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(desktopMixedTransitionHandler
+ .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
+ .thenReturn(Binder())
controller.moveTaskToFront(task.taskId, remoteTransition = null)
- val wct = getLatestWct(type = TRANSIT_OPEN)
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
wct.assertReorderAt(1, freeformTasks[0], toTop = false)
@@ -1695,7 +1716,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun onDesktopWindowMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
+ fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
val task = setUpFreeformTask()
val transition = Binder()
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
@@ -1791,7 +1812,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
.thenReturn(transition)
whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task)))
- .thenReturn(runOnTransit)
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = task.taskId,
+ runOnTransitionStart = runOnTransit,
+ ))
controller.minimizeTask(task)
@@ -2382,8 +2406,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
// Task is being minimized so mark it as not visible.
- taskRepository
- .updateTaskVisibility(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
assertNull(result, "Should not handle request")
@@ -2497,8 +2520,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
// Task is being minimized so mark it as not visible.
- taskRepository
- .updateTaskVisibility(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
assertNull(result, "Should not handle request")
@@ -2573,8 +2595,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
task3.isFocused = false
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
- taskRepository.updateTaskVisibility(DEFAULT_DISPLAY, task3.taskId,
- visible = false)
+ taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
@@ -2897,6 +2918,123 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ Rect(100, 50, 500, 1000), /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration)
+
+ // Assert the task exits desktop mode
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ Rect(100, 50, 500, 1000), /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration)
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ }
+
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() {
+ val task = setUpFreeformTask(bounds = STABLE_BOUNDS)
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ currentDragBounds, /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration)
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator).start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(STABLE_BOUNDS),
+ anyOrNull(),
+ )
+ }
+
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -2928,8 +3066,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
task3.isFocused = false
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
- taskRepository.updateTaskVisibility(DEFAULT_DISPLAY, task3.taskId,
- visible = false)
+ taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
@@ -3105,7 +3242,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
.thenReturn(transition)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)))
- .thenReturn(runOnStartTransit)
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = immersiveTask.taskId,
+ runOnTransitionStart = runOnStartTransit,
+ ))
runOpenInstance(immersiveTask, freeformTask.taskId)
@@ -3205,7 +3345,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
eq(mockSurface),
eq(currentDragBounds),
eq(bounds),
- eq(true)
+ anyOrNull(),
)
verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
ResizeTrigger.SNAP_LEFT_MENU,
@@ -3262,7 +3402,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
eq(mockSurface),
eq(currentDragBounds),
eq(preDragBounds),
- eq(false)
+ any(),
)
verify(desktopModeEventLogger, never()).logTaskResizingStarted(
any(),
@@ -3422,7 +3562,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
}
-
@Test
fun onUnhandledDrag_newFreeformIntent() {
testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
@@ -3507,7 +3646,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
val runOnStartTransit = RunOnStartTransitionCallback()
val transition = Binder()
whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)).thenReturn(runOnStartTransit)
+ .exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = 5,
+ runOnTransitionStart = runOnStartTransit,
+ ))
whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
@@ -3524,7 +3667,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
val runOnStartTransit = RunOnStartTransitionCallback()
val transition = Binder()
whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)).thenReturn(runOnStartTransit)
+ .exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = 5,
+ runOnTransitionStart = runOnStartTransit,
+ ))
whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
@@ -3541,8 +3688,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
- .thenReturn(runOnStartTransit)
- whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = 5,
+ runOnTransitionStart = runOnStartTransit,
+ ))
+ whenever(desktopMixedTransitionHandler
+ .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull()))
+ .thenReturn(transition)
controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -3558,8 +3710,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
val transition = Binder()
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
- .thenReturn(runOnStartTransit)
- whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+ .thenReturn(DesktopImmersiveController.ExitResult.Exit(
+ exitingTask = 5,
+ runOnTransitionStart = runOnStartTransit,
+ ))
+ whenever(desktopMixedTransitionHandler
+ .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
+ .thenReturn(transition)
controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -3808,11 +3965,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
} else {
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
}
- if (active) {
- taskRepository.addActiveTask(displayId, task.taskId)
- taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
- }
- taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
+ taskRepository.addTask(displayId, task.taskId, isVisible = active)
if (!background) {
runningTasks.add(task)
}
@@ -3915,13 +4068,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
private fun markTaskVisible(task: RunningTaskInfo) {
- taskRepository.updateTaskVisibility(
- task.displayId, task.taskId, visible = true)
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = true)
}
private fun markTaskHidden(task: RunningTaskInfo) {
- taskRepository.updateTaskVisibility(
- task.displayId, task.taskId, visible = false)
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
}
private fun getLatestWct(
@@ -3929,7 +4080,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
handlerClass: Class<out TransitionHandler>? = null
): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-
if (handlerClass == null) {
verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
@@ -3948,6 +4098,16 @@ class DesktopTasksControllerTest : ShellTestCase() {
return arg.value
}
+ private fun getLatestDesktopMixedTaskWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ ): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
+ return arg.value
+ }
+
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
@@ -4046,7 +4206,6 @@ private fun WindowContainerTransaction.assertNoRemoveAt(index: Int, token: Windo
}
private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowContainerToken) {
-
assertIndexInBounds(index)
val op = hierarchyOps[index]
assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
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 4d7e47fa51bd..01b69aed8465 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
@@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
@@ -89,6 +90,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Mock lateinit var handler: Handler
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
@@ -106,7 +108,13 @@ class DesktopTasksLimiterTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
desktopTaskRepo =
- DesktopRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ testScope
+ )
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
@@ -248,8 +256,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
- desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
- desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
val wct = WindowContainerTransaction()
@@ -487,29 +495,26 @@ class DesktopTasksLimiterTest : ShellTestCase() {
verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
- private fun setUpFreeformTask(
- displayId: Int = DEFAULT_DISPLAY,
- ): RunningTaskInfo {
+ private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
`when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- desktopTaskRepo.addActiveTask(displayId, task.taskId)
- desktopTaskRepo.addOrMoveFreeformTaskToTop(displayId, task.taskId)
+ desktopTaskRepo.addTask(displayId, task.taskId, task.isVisible)
return task
}
private fun markTaskVisible(task: RunningTaskInfo) {
- desktopTaskRepo.updateTaskVisibility(
+ desktopTaskRepo.updateTask(
task.displayId,
task.taskId,
- visible = true
+ isVisible = true
)
}
private fun markTaskHidden(task: RunningTaskInfo) {
- desktopTaskRepo.updateTaskVisibility(
+ desktopTaskRepo.updateTask(
task.displayId,
task.taskId,
- visible = false
+ isVisible = false
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index fefa933c5208..a82e5e8434b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -19,8 +19,6 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
import static org.junit.Assert.assertTrue;
@@ -28,6 +26,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.animation.AnimatorTestRule;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -36,6 +35,8 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.os.Handler;
import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -53,17 +54,23 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.function.Supplier;
/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+
@Mock
private Transitions mTransitions;
@Mock
@@ -105,7 +112,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
}
@Test
- public void testTransitExitDesktopModeAnimation() throws Throwable {
+ public void testTransitExitDesktopModeAnimation() {
final int transitionType = TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -118,21 +125,16 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
TransitionInfo info = createTransitionInfo(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN, change);
- ArrayList<Exception> exceptions = new ArrayList<>();
- runOnUiThread(() -> {
- try {
- assertTrue(mExitDesktopTaskTransitionHandler
- .startAnimation(mToken, info,
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mTransitionFinishCallback));
- } catch (Exception e) {
- exceptions.add(e);
- }
- });
- if (!exceptions.isEmpty()) {
- throw exceptions.get(0);
- }
+
+ final boolean animated = mExitDesktopTaskTransitionHandler
+ .startAnimation(mToken, info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mTransitionFinishCallback);
+ mAnimatorTestRule.advanceTimeBy(
+ ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION);
+
+ assertTrue(animated);
}
private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
new file mode 100644
index 000000000000..975342902814
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.persistence
+
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+class DesktopRepositoryInitializerTest : ShellTestCase() {
+
+ private lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ private lateinit var shellInit: ShellInit
+ private lateinit var datastoreScope: CoroutineScope
+
+ private lateinit var desktopRepository: DesktopRepository
+ private val persistentRepository = mock<DesktopPersistentRepository>()
+ private val testExecutor = mock<ShellExecutor>()
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ shellInit = spy(ShellInit(testExecutor))
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ repositoryInitializer =
+ DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope)
+ desktopRepository =
+ DesktopRepository(
+ context, shellInit, persistentRepository, repositoryInitializer, datastoreScope)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun initWithPersistence_multipleTasks_addedCorrectly() =
+ runTest(StandardTestDispatcher()) {
+ val freeformTasksInZOrder = listOf(1, 2, 3)
+ whenever(persistentRepository.readDesktop(any(), any()))
+ .thenReturn(
+ Desktop.newBuilder()
+ .setDesktopId(1)
+ .addAllZOrderedTasks(freeformTasksInZOrder)
+ .putTasksByTaskId(
+ 1,
+ DesktopTask.newBuilder()
+ .setTaskId(1)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build())
+ .putTasksByTaskId(
+ 2,
+ DesktopTask.newBuilder()
+ .setTaskId(2)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build())
+ .putTasksByTaskId(
+ 3,
+ DesktopTask.newBuilder()
+ .setTaskId(3)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build())
+ .build())
+
+ repositoryInitializer.initialize(desktopRepository)
+
+ verify(persistentRepository).readDesktop(any(), any())
+ assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY))
+ .containsExactly(1, 2, 3)
+ .inOrder()
+ assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY))
+ .containsExactly(1, 2)
+ .inOrder()
+ assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3)
+ }
+
+ @After
+ fun tearDown() {
+ datastoreScope.cancel()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 8dd154566361..b504a88e71fc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -23,6 +23,8 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION;
+import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
+import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
@@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
@@ -59,9 +62,8 @@ import org.mockito.quality.Strictness;
import java.util.Optional;
/**
- * Tests for {@link FreeformTaskListener}
- * Build/Install/Run:
- * atest WMShellUnitTests:FreeformTaskListenerTests
+ * Tests for {@link FreeformTaskListener} Build/Install/Run: atest
+ * WMShellUnitTests:FreeformTaskListenerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -84,53 +86,99 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
private DesktopTasksController mDesktopTasksController;
@Mock
private LaunchAdjacentController mLaunchAdjacentController;
+ @Mock
+ private TaskChangeListener mTaskChangeListener;
+
private FreeformTaskListener mFreeformTaskListener;
private StaticMockitoSession mMockitoSession;
@Before
public void setup() {
- mMockitoSession = mockitoSession().initMocks(this)
- .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking();
+ mMockitoSession =
+ mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus.class)
+ .startMocking();
doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
- mFreeformTaskListener = new FreeformTaskListener(
- mContext,
- mShellInit,
- mTaskOrganizer,
- Optional.of(mDesktopRepository),
- Optional.of(mDesktopTasksController),
- mLaunchAdjacentController,
- mWindowDecorViewModel);
+ mFreeformTaskListener =
+ new FreeformTaskListener(
+ mContext,
+ mShellInit,
+ mTaskOrganizer,
+ Optional.of(mDesktopRepository),
+ Optional.of(mDesktopTasksController),
+ mLaunchAdjacentController,
+ mWindowDecorViewModel,
+ Optional.of(mTaskChangeListener));
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_noTransitionObservers_visibleTask_addsTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible = true);
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_noTransitionObservers_nonVisibleTask_addsTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = false;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_useTransitionObserver_noopInRepository() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible);
}
@Test
- public void testFocusTaskChanged_freeformTaskIsAddedToRepo() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ public void focusTaskChanged_addsFreeformTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isFocused = true;
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopRepository)
- .addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
+ verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
}
@Test
- public void testFocusTaskChanged_fullscreenTaskIsNotAddedToRepo() {
- ActivityManager.RunningTaskInfo fullscreenTask = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ public void focusTaskChanged_fullscreenTaskNotAddedToRepo() {
+ ActivityManager.RunningTaskInfo fullscreenTask =
+ new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build();
fullscreenTask.isFocused = true;
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
verify(mDesktopRepository, never())
- .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
+ .addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible);
}
@Test
- public void testVisibilityTaskChanged_visible_setLaunchAdjacentDisabled() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ public void visibilityTaskChanged_visible_setLaunchAdjacentDisabled() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -139,9 +187,9 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
- public void testVisibilityTaskChanged_NotVisible_setLaunchAdjacentEnabled() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ public void visibilityTaskChanged_notVisible_setLaunchAdjacentEnabled() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -154,9 +202,10 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- public void onTaskVanished_nonClosingTask_isMinimized() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskVanished_nonClosingTask_noTransitionObservers_isMinimized() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -169,10 +218,11 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- public void onTaskVanished_closingTask_isNotMinimized() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ public void onTaskVanished_closingTask_noTransitionObservers_isNotMinimized() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -188,9 +238,23 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskVanished_usesTransitionObservers_noopInRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository, never()).removeClosingTask(task.taskId);
+ verify(mDesktopRepository, never()).removeFreeformTask(task.displayId, task.taskId);
+ }
+
+ @Test
public void onTaskInfoChanged_withDesktopController_forwards() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -199,6 +263,36 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
verify(mDesktopTasksController).onTaskInfoChanged(task);
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskInfoChanged_noTransitionObservers_updatesTask() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mTaskChangeListener, never()).onTaskChanging(any());
+ verify(mDesktopRepository).updateTask(task.displayId, task.taskId, task.isVisible);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ public void onTaskInfoChanged_useTransitionObserver_noopInRepository() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mTaskChangeListener).onNonTransitionTaskChanging(any());
+ verify(mDesktopRepository, never())
+ .updateTask(task.displayId, task.taskId, task.isVisible);
+ }
+
@After
public void tearDown() {
mMockitoSession.finishMocking();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 90ab2b8285cd..5aed4611cdc8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -58,25 +58,17 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
-/**
- * Tests for {@link FreeformTaskTransitionObserver}.
- */
+/** Tests for {@link FreeformTaskTransitionObserver}. */
@SmallTest
public class FreeformTaskTransitionObserverTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Mock
- private ShellInit mShellInit;
- @Mock
- private Transitions mTransitions;
- @Mock
- private DesktopImmersiveController mDesktopImmersiveController;
- @Mock
- private WindowDecorViewModel mWindowDecorViewModel;
- @Mock
- private TaskChangeListener mTaskChangeListener;
- @Mock
- private FocusTransitionObserver mFocusTransitionObserver;
+ @Mock private ShellInit mShellInit;
+ @Mock private Transitions mTransitions;
+ @Mock private DesktopImmersiveController mDesktopImmersiveController;
+ @Mock private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock private TaskChangeListener mTaskChangeListener;
+ @Mock private FocusTransitionObserver mFocusTransitionObserver;
private FreeformTaskTransitionObserver mTransitionObserver;
@@ -85,20 +77,22 @@ public class FreeformTaskTransitionObserverTest {
MockitoAnnotations.initMocks(this);
PackageManager pm = mock(PackageManager.class);
- doReturn(true).when(pm).hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ doReturn(true).when(pm).hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
final Context context = mock(Context.class);
doReturn(pm).when(context).getPackageManager();
- mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions,
- Optional.of(mDesktopImmersiveController),
- mWindowDecorViewModel, Optional.of(mTaskChangeListener), mFocusTransitionObserver);
-
- final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
- Runnable.class);
- verify(mShellInit).addInitCallback(initRunnableCaptor.capture(),
- same(mTransitionObserver));
+ mTransitionObserver =
+ new FreeformTaskTransitionObserver(
+ context,
+ mShellInit,
+ mTransitions,
+ Optional.of(mDesktopImmersiveController),
+ mWindowDecorViewModel,
+ Optional.of(mTaskChangeListener),
+ mFocusTransitionObserver);
+
+ final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), same(mTransitionObserver));
initRunnableCaptor.getValue().run();
}
@@ -109,10 +103,9 @@ public class FreeformTaskTransitionObserverTest {
@Test
public void openTransition_createsWindowDecor() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(change).build();
+ final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -120,16 +113,15 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).onTaskOpening(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ verify(mWindowDecorViewModel)
+ .onTaskOpening(change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
public void openTransition_notifiesOnTaskOpening() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(change).build();
+ final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -144,8 +136,10 @@ public class FreeformTaskTransitionObserverTest {
public void toFrontTransition_notifiesOnTaskMovingToFront() {
final TransitionInfo.Change change =
createChange(TRANSIT_TO_FRONT, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0)
+ .addChange(change)
+ .build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -160,8 +154,10 @@ public class FreeformTaskTransitionObserverTest {
public void toBackTransition_notifiesOnTaskMovingToBack() {
final TransitionInfo.Change change =
createChange(TRANSIT_TO_BACK, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_BACK, /* flags= */ 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_TO_BACK, /* flags= */ 0)
+ .addChange(change)
+ .build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -173,11 +169,11 @@ public class FreeformTaskTransitionObserverTest {
}
@Test
- public void changeTransition_notifiesOnTaskChanging() {
+ public void changeTransition_notifiesOnTaskChange() {
final TransitionInfo.Change change =
createChange(TRANSIT_CHANGE, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -192,8 +188,8 @@ public class FreeformTaskTransitionObserverTest {
public void closeTransition_preparesWindowDecor() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -201,16 +197,15 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).onTaskClosing(
- change.getTaskInfo(), startT, finishT);
+ verify(mWindowDecorViewModel).onTaskClosing(change.getTaskInfo(), startT, finishT);
}
@Test
public void closeTransition_notifiesOnTaskClosing() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -225,8 +220,8 @@ public class FreeformTaskTransitionObserverTest {
public void closeTransition_doesntCloseWindowDecorDuringTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -241,8 +236,8 @@ public class FreeformTaskTransitionObserverTest {
public void closeTransition_closesWindowDecorAfterTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final AutoCloseable windowDecor = mock(AutoCloseable.class);
@@ -261,8 +256,8 @@ public class FreeformTaskTransitionObserverTest {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(change1).build();
+ final TransitionInfo info1 =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -273,8 +268,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change2).build();
+ final TransitionInfo info2 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
@@ -292,8 +287,8 @@ public class FreeformTaskTransitionObserverTest {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change1).build();
+ final TransitionInfo info1 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -304,8 +299,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change2).build();
+ final TransitionInfo info2 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
@@ -368,9 +363,10 @@ public class FreeformTaskTransitionObserverTest {
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
- final TransitionInfo.Change change = new TransitionInfo.Change(
- new WindowContainerToken(mock(IWindowContainerToken.class)),
- mock(SurfaceControl.class));
+ final TransitionInfo.Change change =
+ new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)),
+ mock(SurfaceControl.class));
change.setMode(mode);
change.setTaskInfo(taskInfo);
return change;
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 e74c804d4f40..bcb7461bfae7 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
@@ -280,7 +280,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private void preparePipSurfaceTransactionHelper() {
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
- .crop(any(), any(), any());
+ .cropAndPosition(any(), any(), any());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.resetScale(any(), any(), any());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
new file mode 100644
index 000000000000..9cc18ffdaed7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipAlphaAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipAlphaAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ private PipAlphaAnimator mPipAlphaAnimator;
+ private SurfaceControl mTestLeash;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipAlphaAnimatorTest")
+ .setCallsite("PipAlphaAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_fadeInAnimator_callbackStartCallback() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
+ PipAlphaAnimator.FADE_IN);
+
+ mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_fadeInAnimator_callbackStartAndEndCallback() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
+ PipAlphaAnimator.FADE_IN);
+
+ mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockStartCallback).run();
+ }
+
+ @Test
+ public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
+ PipAlphaAnimator.FADE_IN);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockTransaction).setAlpha(mTestLeash, 1.0f);
+ }
+
+ @Test
+ public void onAnimationEnd_fadeOutAnimator_leashInvisibleAtEnd() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
+ PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockTransaction).setAlpha(mTestLeash, 0f);
+ }
+}
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
new file mode 100644
index 000000000000..e19a10a78417
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+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.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipExpandAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipExpandAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ private PipExpandAnimator mPipExpandAnimator;
+ private Rect mBaseBounds;
+ private Rect mStartBounds;
+ private Rect mEndBounds;
+ private Rect mSourceRectHint;
+ @Surface.Rotation private int mRotation;
+ private SurfaceControl mTestLeash;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ // No-op on the mMockTransaction
+ when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ // Do the same for mMockFinishTransaction
+ when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipExpandAnimatorTest")
+ .setCallsite("PipExpandAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_expand_callbackStartCallback() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ mPipExpandAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_expand_callbackStartAndEndCallback() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ mPipExpandAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void onAnimationEnd_expand_leashIsFullscreen() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipExpandAnimator.end();
+ });
+
+ verify(mMockTransaction).setCrop(mTestLeash, mEndBounds);
+ verify(mMockTransaction).setCornerRadius(mTestLeash, 0f);
+ verify(mMockTransaction).setShadowRadius(mTestLeash, 0f);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
new file mode 100644
index 000000000000..0adb50b81896
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+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 PipResizeAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipResizeAnimatorTest {
+
+ private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+
+ @Mock private Context mMockContext;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ private PipResizeAnimator mPipResizeAnimator;
+ private Rect mBaseBounds;
+ private Rect mStartBounds;
+ private Rect mEndBounds;
+ private SurfaceControl mTestLeash;
+ private ArgumentCaptor<Matrix> mArgumentCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockStartTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+
+ mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipResizeAnimatorTest")
+ .setCallsite("PipResizeAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_resize_callbackStartCallback() {
+ mBaseBounds = new Rect(100, 100, 500, 500);
+ mStartBounds = new Rect(200, 200, 1_000, 1_000);
+ mEndBounds = new Rect(mBaseBounds);
+ final int duration = 10;
+ final float delta = 0;
+ mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds,
+ duration, delta);
+
+ mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipResizeAnimator.start();
+ mPipResizeAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_resize_callbackStartAndEndCallback() {
+ mBaseBounds = new Rect(100, 100, 500, 500);
+ mStartBounds = new Rect(200, 200, 1_000, 1_000);
+ mEndBounds = new Rect(mBaseBounds);
+ final int duration = 10;
+ final float delta = 0;
+ mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds,
+ duration, delta);
+
+ mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipResizeAnimator.start();
+ mPipResizeAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void onAnimationEnd_resizeDown_sizeChanged() {
+ // Resize from 800x800 to 400x400, eg. resize down
+ mBaseBounds = new Rect(100, 100, 500, 500);
+ mStartBounds = new Rect(200, 200, 1_000, 1_000);
+ mEndBounds = new Rect(mBaseBounds);
+ final int duration = 10;
+ final float delta = 0;
+ final float[] matrix = new float[9];
+ mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds,
+ duration, delta);
+ mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipResizeAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipResizeAnimator.end();
+ });
+
+ // Start transaction scales down from its final state
+ verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X],
+ mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y],
+ mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Final animation transaction scales to 1 and puts the leash at final position
+ verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Finish transaction resets scale and puts the leash at final position
+ verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+ }
+
+ @Test
+ public void onAnimationEnd_resizeUp_sizeChanged() {
+ // Resize from 400x400 to 800x800, eg. resize up
+ mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+ mStartBounds = new Rect(100, 100, 500, 500);
+ mEndBounds = new Rect(mBaseBounds);
+ final int duration = 10;
+ final float delta = 0;
+ final float[] matrix = new float[9];
+ mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds,
+ duration, delta);
+ mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipResizeAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipResizeAnimator.end();
+ });
+
+ // Start transaction scales up from its final state
+ verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X],
+ mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y],
+ mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Final animation transaction scales to 1 and puts the leash at final position
+ verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Finish transaction resets scale and puts the leash at final position
+ verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+ }
+
+ @Test
+ public void onAnimationEnd_withInitialDelta_rotateToZeroDegree() {
+ mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+ mStartBounds = new Rect(100, 100, 500, 500);
+ mEndBounds = new Rect(mBaseBounds);
+ final int duration = 10;
+ final float delta = 45;
+ final float[] matrix = new float[9];
+ mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds,
+ duration, delta);
+ mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipResizeAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipResizeAnimator.end();
+ });
+
+ // Final animation transaction sets skew to zero
+ verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+ // Finish transaction sets skew to zero
+ verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+ mArgumentCaptor.getValue().getValues(matrix);
+ assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+ assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index afdb68776d04..efe4fb18f273 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -34,6 +34,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import org.junit.Before
@@ -107,8 +108,8 @@ class TaskStackTransitionObserverTest {
callOnTransitionFinished()
executor.flushAll()
- assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
- assertThat(listener.taskInfoToBeNotified.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
.isEqualTo(change.taskInfo?.windowingMode)
}
@@ -130,8 +131,8 @@ class TaskStackTransitionObserverTest {
callOnTransitionFinished()
executor.flushAll()
- assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(1)
- assertThat(listener.taskInfoToBeNotified.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(1)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
.isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
}
@@ -161,9 +162,9 @@ class TaskStackTransitionObserverTest {
callOnTransitionFinished()
executor.flushAll()
- assertThat(listener.taskInfoToBeNotified.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
.isEqualTo(freeformOpenChange.taskInfo?.taskId)
- assertThat(listener.taskInfoToBeNotified.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
.isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
}
@@ -199,9 +200,15 @@ class TaskStackTransitionObserverTest {
callOnTransitionFinished()
executor.flushAll()
- assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
- assertThat(listener.taskInfoToBeNotified.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
.isEqualTo(change.taskInfo?.windowingMode)
+
+ assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+ with(listener.taskInfoOnTaskChanged.last()) {
+ assertThat(taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+ assertThat(windowingMode).isEqualTo(mergedChange.taskInfo?.windowingMode)
+ }
}
@Test
@@ -236,18 +243,151 @@ class TaskStackTransitionObserverTest {
callOnTransitionFinished()
executor.flushAll()
- assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
- assertThat(listener.taskInfoToBeNotified.windowingMode)
- .isEqualTo(mergedChange.taskInfo?.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+ .isEqualTo(mergedChange.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+ .isEqualTo(mergedChange.taskInfo?.windowingMode)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun taskChange_freeformWindowToFullscreenWindow_listenerNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+ val freeformState =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfoOpen =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(freeformState).build()
+ callOnTransitionReady(transitionInfoOpen)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+ .isEqualTo(freeformState.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+ .isEqualTo(freeformState.taskInfo?.windowingMode)
+ assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+ // create change transition to update the windowing mode to full screen.
+ val fullscreenState =
+ createChange(
+ WindowManager.TRANSIT_CHANGE,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ )
+ val transitionInfoChange =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+ .addChange(fullscreenState)
+ .build()
+
+ callOnTransitionReady(transitionInfoChange)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ // Asserting whether freeformState remains the same as before the change
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+ .isEqualTo(freeformState.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+ // Asserting changes
+ assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+ with(listener.taskInfoOnTaskChanged.last()) {
+ assertThat(taskId).isEqualTo(fullscreenState.taskInfo?.taskId)
+ assertThat(isFullscreen).isEqualTo(true)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun singleTransition_withOpenAndChange_onlyOpenIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Creating multiple changes to be fired in a single transition
+ val freeformState =
+ createChange(
+ mode = WindowManager.TRANSIT_OPEN,
+ taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val fullscreenState =
+ createChange(
+ mode = WindowManager.TRANSIT_CHANGE,
+ taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ )
+
+ val transitionInfoWithChanges =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+ .addChange(freeformState)
+ .addChange(fullscreenState)
+ .build()
+
+ callOnTransitionReady(transitionInfoWithChanges)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+ .isEqualTo(freeformState.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+ assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun singleTransition_withMultipleChanges_listenerNotified_forEachChange() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ val taskId = 1
+
+ // Creating multiple changes to be fired in a single transition
+ val changes =
+ listOf(
+ WindowConfiguration.WINDOWING_MODE_FREEFORM,
+ WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+ )
+ .map { change ->
+ createChange(
+ mode = WindowManager.TRANSIT_CHANGE,
+ taskInfo = createTaskInfo(taskId, change)
+ )
+ }
+
+ val transitionInfoWithChanges =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+ .apply { changes.forEach { c -> this@apply.addChange(c) } }
+ .build()
+
+ callOnTransitionReady(transitionInfoWithChanges)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(changes.size)
+ changes.forEachIndexed { index, change ->
+ assertThat(listener.taskInfoOnTaskChanged[index].taskId)
+ .isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoOnTaskChanged[index].windowingMode)
+ .isEqualTo(change.taskInfo?.windowingMode)
+ }
}
class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
- var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
+ var taskInfoOnTaskMovedToFront = ActivityManager.RunningTaskInfo()
+ var taskInfoOnTaskChanged = mutableListOf<ActivityManager.RunningTaskInfo>()
override fun onTaskMovedToFrontThroughTransition(
taskInfo: ActivityManager.RunningTaskInfo
) {
- taskInfoToBeNotified = taskInfo
+ taskInfoOnTaskMovedToFront = taskInfo
+ }
+
+ override fun onTaskChangedThroughTransition(taskInfo: ActivityManager.RunningTaskInfo) {
+ taskInfoOnTaskChanged += taskInfo
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt
new file mode 100644
index 000000000000..c19232b6f787
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.shared.animation
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.app.animation.Interpolators
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowAnimatorTest {
+
+ private val transaction = mock<SurfaceControl.Transaction>()
+ private val change = mock<TransitionInfo.Change>()
+ private val leash = mock<SurfaceControl>()
+
+ private val displayMetrics = DisplayMetrics().apply { density = 1f }
+
+ private val positionXArgumentCaptor = argumentCaptor<Float>()
+ private val positionYArgumentCaptor = argumentCaptor<Float>()
+ private val scaleXArgumentCaptor = argumentCaptor<Float>()
+ private val scaleYArgumentCaptor = argumentCaptor<Float>()
+
+ @Before
+ fun setup() {
+ whenever(change.leash).thenReturn(leash)
+ whenever(change.endAbsBounds).thenReturn(END_BOUNDS)
+ whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction)
+ whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction)
+ whenever(
+ transaction.setPosition(
+ any(),
+ positionXArgumentCaptor.capture(),
+ positionYArgumentCaptor.capture(),
+ )
+ )
+ .thenReturn(transaction)
+ whenever(
+ transaction.setScale(
+ any(),
+ scaleXArgumentCaptor.capture(),
+ scaleYArgumentCaptor.capture(),
+ )
+ )
+ .thenReturn(transaction)
+ }
+
+ @Test
+ fun createBoundsAnimator_returnsCorrectDefaultAnimatorParams() = runOnUiThread {
+ val boundsAnimParams =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 100L,
+ interpolator = Interpolators.STANDARD_ACCELERATE,
+ )
+
+ val valueAnimator =
+ WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ boundsAnimParams,
+ change,
+ transaction
+ )
+ valueAnimator.start()
+
+ assertThat(valueAnimator.duration).isEqualTo(100L)
+ assertThat(valueAnimator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE)
+ val expectedPosition = PointF(END_BOUNDS.left.toFloat(), END_BOUNDS.top.toFloat())
+ assertTransactionParams(expectedPosition, expectedScale = PointF(1f, 1f))
+ }
+
+ @Test
+ fun createBoundsAnimator_startScaleAndOffset_correctPosAndScale() = runOnUiThread {
+ val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+ whenever(change.endAbsBounds).thenReturn(bounds)
+ val boundsAnimParams =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 100L,
+ startOffsetYDp = 10f,
+ startScale = 0.5f,
+ interpolator = Interpolators.STANDARD_ACCELERATE,
+ )
+
+ val valueAnimator =
+ WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ boundsAnimParams,
+ change,
+ transaction
+ )
+ valueAnimator.start()
+
+ assertTransactionParams(
+ expectedPosition = PointF(150f, 260f),
+ expectedScale = PointF(0.5f, 0.5f),
+ )
+ }
+
+ @Test
+ fun createBoundsAnimator_endScaleAndOffset_correctPosAndScale() = runOnUiThread {
+ val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+ whenever(change.endAbsBounds).thenReturn(bounds)
+ val boundsAnimParams =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 100L,
+ endOffsetYDp = 10f,
+ endScale = 0.5f,
+ interpolator = Interpolators.STANDARD_ACCELERATE,
+ )
+
+ val valueAnimator =
+ WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ boundsAnimParams,
+ change,
+ transaction
+ )
+ valueAnimator.start()
+ valueAnimator.end()
+
+ assertTransactionParams(
+ expectedPosition = PointF(150f, 260f),
+ expectedScale = PointF(0.5f, 0.5f),
+ )
+ }
+
+ @Test
+ fun createBoundsAnimator_middleOfAnimation_correctPosAndScale() = runOnUiThread {
+ val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+ whenever(change.endAbsBounds).thenReturn(bounds)
+ val boundsAnimParams =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 100L,
+ endOffsetYDp = 10f,
+ startScale = 0.5f,
+ endScale = 0.9f,
+ interpolator = Interpolators.LINEAR,
+ )
+
+ val valueAnimator =
+ WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ boundsAnimParams,
+ change,
+ transaction
+ )
+ valueAnimator.currentPlayTime = 50
+
+ assertTransactionParams(
+ // We should have a window of size 140x140, which we centre by placing at pos 130, 230.
+ // Then add 10*0.5 as y-offset
+ expectedPosition = PointF(130f, 235f),
+ expectedScale = PointF(0.7f, 0.7f),
+ )
+ }
+
+ private fun assertTransactionParams(expectedPosition: PointF, expectedScale: PointF) {
+ assertThat(positionXArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedPosition.x)
+ assertThat(positionYArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedPosition.y)
+ assertThat(scaleXArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedScale.x)
+ assertThat(scaleYArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedScale.y)
+ }
+
+ companion object {
+ private val END_BOUNDS =
+ Rect(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
+
+ private const val TOLERANCE = 1e-3f
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index f9f760e3f482..1215c52209a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -64,13 +64,14 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
context = context,
shellInit = ShellInit(TestShellExecutor()),
persistentRepository = mock(),
+ repositoryInitializer = mock(),
mainCoroutineScope = mock()
)
}
@After
fun tearDown() {
- menu.close()
+ menu.animateClose()
}
@Test
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 c42be7fef2a6..36c5be1d7191 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
@@ -946,7 +946,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testDecor_onClickToSplitScreen_detachesStatusBarInputLayer() {
+ fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
val toSplitScreenListenerCaptor = forClass(Function0::class.java)
as ArgumentCaptor<Function0<Unit>>
val decor = createOpenTaskDecoration(
@@ -956,7 +956,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
toSplitScreenListenerCaptor.value.invoke()
- verify(decor).detachStatusBarInputLayer()
+ verify(decor).disposeStatusBarInputLayer()
}
@Test
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 8a2c7782906d..f6fed29bbbe8 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
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.times;
import android.app.ActivityManager;
import android.app.assist.AssistContent;
@@ -849,7 +850,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockHandler).post(runnableArgument.capture());
+ // Once for view host, the other for the AppHandle input layer.
+ verify(mMockHandler, times(2)).post(runnableArgument.capture());
runnableArgument.getValue().run();
verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
}
@@ -876,7 +878,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockHandler).post(runnableArgument.capture());
+ // Once for view host, the other for the AppHandle input layer.
+ verify(mMockHandler, times(2)).post(runnableArgument.capture());
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
@@ -890,7 +893,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockHandler).post(runnableArgument.capture());
+ // Once for view host, the other for the AppHandle input layer.
+ verify(mMockHandler, times(2)).post(runnableArgument.capture());
spyWindowDecor.close();
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 cb7fadee9822..8e0434cb28f7 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
@@ -218,8 +218,6 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
-
assertNull(mRelayoutResult.mRootView);
}
@@ -281,8 +279,6 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlStartT)
- .show(mMockTaskSurface);
verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
@@ -863,7 +859,7 @@ public class WindowDecorationTests extends ShellTestCase {
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- mRelayoutParams.mSetTaskPositionAndCrop = false;
+ mRelayoutParams.mSetTaskVisibilityPositionAndCrop = false;
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT, never()).setWindowCrop(
@@ -891,7 +887,7 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- mRelayoutParams.mSetTaskPositionAndCrop = true;
+ mRelayoutParams.mSetTaskVisibilityPositionAndCrop = true;
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT).setWindowCrop(
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 0ccd424c2b30..80ad1df44a1b 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
@@ -36,9 +36,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -73,6 +75,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
returnToDragStartAnimatorMock,
desktopRepository,
)
+ whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
}
@Test
@@ -143,7 +146,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
}
@Test
- fun userChange_starting_allTilingSessionsShouldBeDestroyed() {
+ fun onUserChange_allTilingSessionsShouldBeDestroyed() {
desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
1,
desktopTilingDecoration,
@@ -155,7 +158,29 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
desktopTilingDecorViewModel.onUserChange()
- verify(desktopTilingDecoration, times(2)).onUserChange()
+ verify(desktopTilingDecoration, times(2)).resetTilingSession()
+ }
+
+ @Test
+ fun displayOrientationChange_tilingForDisplayShouldBeDestroyed() {
+ desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+ 1,
+ desktopTilingDecoration,
+ )
+ desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+ 2,
+ desktopTilingDecoration,
+ )
+
+ desktopTilingDecorViewModel.onDisplayChange(1, 1, 2, null, null)
+
+ verify(desktopTilingDecoration, times(1)).resetTilingSession()
+ verify(displayControllerMock, times(1))
+ .addDisplayChangingController(eq(desktopTilingDecorViewModel))
+
+ desktopTilingDecorViewModel.onDisplayChange(1, 1, 3, null, null)
+ // No extra calls after 180 degree change.
+ verify(desktopTilingDecoration, times(1)).resetTilingSession()
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 0ee3f4695e85..3143946fa828 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -16,9 +16,12 @@
package com.android.wm.shell.windowdecor.tiling
+import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.RoundedCorner
import android.view.SurfaceControl
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
@@ -29,6 +32,7 @@ import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -55,10 +59,17 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
private lateinit var desktopTilingWindowManager: DesktopTilingDividerWindowManager
+ private val context = mock<Context>()
+ private val display = mock<Display>()
+ private val roundedCorner = mock<RoundedCorner>()
+
@Before
fun setup() {
config = Configuration()
config.setToDefaults()
+ whenever(context.display).thenReturn(display)
+ whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
+ whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
desktopTilingWindowManager =
DesktopTilingDividerWindowManager(
config,
@@ -69,6 +80,7 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
transitionHandlerMock,
transactionSupplierMock,
BOUNDS,
+ context,
)
}
@@ -85,7 +97,6 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
// Ensure a surfaceControl transaction runs to show the divider.
verify(transactionSupplierMock, times(1)).get()
- verify(syncQueueMock, times(1)).runInSync(any())
desktopTilingWindowManager.release()
verify(transaction, times(1)).hide(any())
@@ -93,7 +104,24 @@ class DesktopTilingDividerWindowManagerTest : ShellTestCase() {
verify(transaction, times(1)).apply()
}
+ @Test
+ @UiThreadTest
+ fun testWindowManager_accountsForRoundedCornerDimensions() {
+ whenever(transactionSupplierMock.get()).thenReturn(transaction)
+ whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.show(any())).thenReturn(transaction)
+
+ desktopTilingWindowManager.generateViewHost(surfaceControl)
+
+ // Ensure a surfaceControl transaction runs to show the divider.
+ verify(transaction, times(1))
+ .setPosition(any(), eq(BOUNDS.left.toFloat() - CORNER_RADIUS), any())
+ }
+
companion object {
private val BOUNDS = Rect(1, 2, 3, 4)
+ private val CORNER_RADIUS = 28
}
}
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 0b04a211f717..f371f5223419 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
@@ -49,6 +49,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.capture
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -112,6 +113,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
returnToDragStartAnimator,
desktopRepository,
)
+ whenever(context.createContextAsUser(any(), any())).thenReturn(context)
}
@Test
@@ -195,7 +197,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
verify(toggleResizeDesktopTaskTransitionHandler, times(1))
.startTransition(capture(wctCaptor), any())
- verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), any())
+ verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), anyOrNull())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getLeftTaskBounds()
@@ -473,12 +475,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
tilingDecoration.desktopTilingDividerWindowManager = desktopTilingDividerWindowManager
- tilingDecoration.onUserChange()
+ tilingDecoration.resetTilingSession()
assertThat(tilingDecoration.leftTaskResizingHelper).isNull()
assertThat(tilingDecoration.rightTaskResizingHelper).isNull()
verify(desktopWindowDecoration, times(2)).removeDragResizeListener(any())
verify(tiledTaskHelper, times(2)).dispose()
+ verify(context, never()).getApplicationContext()
}
private fun initTiledTaskHelperMock(taskInfo: ActivityManager.RunningTaskInfo) {
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 fd5eb8855d32..c96ce955f217 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
@@ -32,8 +32,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -68,6 +70,7 @@ class TilingDividerViewTest : ShellTestCase() {
tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+ whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true)
val motionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_MOVE, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, motionEvent)
@@ -79,6 +82,24 @@ class TilingDividerViewTest : ShellTestCase() {
verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any())
}
+ @Test
+ @UiThreadTest
+ fun testCallbackOnTouch_doesNotHappen_whenNoTouchMove() {
+ val x = 5
+ val y = 5
+ val downTime: Long = SystemClock.uptimeMillis()
+
+ val downMotionEvent =
+ getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
+ tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
+ verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+
+ val upMotionEvent =
+ getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
+ tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
+ verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any())
+ }
+
private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent {
val properties = MotionEvent.PointerProperties()
properties.id = 0
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp
index cf3c0eeff402..e94540544cf5 100644
--- a/libs/androidfw/PngCrunch.cpp
+++ b/libs/androidfw/PngCrunch.cpp
@@ -506,8 +506,7 @@ bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out
// Set up the write functions which write to our custom data sources.
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
- // We want small files and can take the performance hit to achieve this goal.
- png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+ png_set_compression_level(write_ptr, options.compression_level);
// Begin analysis of the image data.
// Scan the entire image and determine if:
diff --git a/libs/androidfw/include/androidfw/Png.h b/libs/androidfw/include/androidfw/Png.h
index 2ece43e08110..72be59b8f313 100644
--- a/libs/androidfw/include/androidfw/Png.h
+++ b/libs/androidfw/include/androidfw/Png.h
@@ -31,6 +31,8 @@ constexpr size_t kPngSignatureSize = 8u;
struct PngOptions {
int grayscale_tolerance = 0;
+ // By default we want small files and can take the performance hit to achieve this goal.
+ int compression_level = 9;
};
/**
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 27817e9eb984..faf84a8ab5ac 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -3,8 +3,9 @@ package com.google.android.appfunctions.sidecar {
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method 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 public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ 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 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>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
@@ -34,6 +35,7 @@ package com.google.android.appfunctions.sidecar {
}
public final class ExecuteAppFunctionResponse {
+ method public int getErrorCategory();
method @Nullable public String getErrorMessage();
method @NonNull public android.os.Bundle getExtras();
method public int getResultCode();
@@ -41,14 +43,19 @@ package com.google.android.appfunctions.sidecar {
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 = 2; // 0x2
- field public static final int RESULT_CANCELLED = 6; // 0x6
- field public static final int RESULT_DENIED = 1; // 0x1
- field public static final int RESULT_DISABLED = 5; // 0x5
- field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
- field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ 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
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index 43377d8eb91c..2075104ff868 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.UserHandleAware;
import android.content.Context;
@@ -41,8 +42,6 @@ import java.util.function.Consumer;
* <p>This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and
* exposes it here as a sidecar library (avoiding direct dependency on the platform API).
*/
-// TODO(b/357551503): Implement get and set enabled app function APIs.
-// TODO(b/367329899): Add sidecar library to Android B builds.
public final class AppFunctionManager {
/**
* The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
@@ -70,9 +69,9 @@ public final class AppFunctionManager {
@IntDef(
prefix = {"APP_FUNCTION_STATE_"},
value = {
- APP_FUNCTION_STATE_DEFAULT,
- APP_FUNCTION_STATE_ENABLED,
- APP_FUNCTION_STATE_DISABLED
+ APP_FUNCTION_STATE_DEFAULT,
+ APP_FUNCTION_STATE_ENABLED,
+ APP_FUNCTION_STATE_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface EnabledState {}
@@ -102,7 +101,16 @@ public final class AppFunctionManager {
* <p>Proxies request and response to the underlying {@link
* android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and
* response in the appropriate type required by the function.
+ *
+ * <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
+ * documented behaviour of this method.
*/
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+ Manifest.permission.EXECUTE_APP_FUNCTIONS
+ },
+ conditional = true)
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@@ -128,25 +136,15 @@ public final class AppFunctionManager {
/**
* Returns a boolean through a callback, indicating whether the app function is enabled.
*
- * <p>* This method can only check app functions owned by the caller, or those where the caller
- * has visibility to the owner package and holds either the {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
- *
- * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
- *
- * <ul>
- * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
- * have access to it.
- * </ul>
- *
- * @param functionIdentifier the identifier of the app function to check (unique within the
- * target package) and in most cases, these are automatically generated by the AppFunctions
- * SDK
- * @param targetPackage the package name of the app function's owner
- * @param executor the executor to run the request
- * @param callback the callback to receive the function enabled check result
+ * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
+ * documented behaviour of this method.
*/
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+ Manifest.permission.EXECUTE_APP_FUNCTIONS
+ },
+ conditional = true)
public void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
@@ -156,22 +154,23 @@ public final class AppFunctionManager {
}
/**
- * Sets the enabled state of the app function owned by the calling package.
- *
- * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ * Returns a boolean through a callback, indicating whether the app function is enabled.
*
- * <ul>
- * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
- * have access to it.
- * </ul>
+ * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
+ * documented behaviour of this method.
+ */
+ public void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ mManager.isAppFunctionEnabled(functionIdentifier, executor, callback);
+ }
+
+ /**
+ * Sets the enabled state of the app function owned by the calling package.
*
- * @param functionIdentifier the identifier of the app function to enable (unique within the
- * calling package). In most cases, identifiers are automatically generated by the
- * AppFunctions SDK
- * @param newEnabledState the new state of the app function
- * @param executor the executor to run the callback
- * @param callback the callback to receive the result of the function enablement. The call was
- * successful if no exception was thrown.
+ * <p>See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the
+ * documented behavoir of this method.
*/
// Constants in @EnabledState should always mirror those in
// android.app.appfunctions.AppFunctionManager.
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
index fa6d2ff12313..593c5213dd52 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
@@ -34,8 +34,8 @@ public final class ExecuteAppFunctionRequest {
@NonNull private final String mTargetPackageName;
/**
- * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
- * Document how callers can get the available function identifiers.
+ * The unique string identifier of the app function to be executed. This identifier is used to
+ * execute a specific app function.
*/
@NonNull private final String mFunctionIdentifier;
@@ -49,8 +49,6 @@ public final class ExecuteAppFunctionRequest {
*
* <p>The document may have missing parameters. Developers are advised to implement defensive
* handling measures.
- *
- * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution
*/
@NonNull private final GenericDocument mParameters;
@@ -71,7 +69,19 @@ public final class ExecuteAppFunctionRequest {
return mTargetPackageName;
}
- /** Returns the unique string identifier of the app function to be executed. */
+ /**
+ * Returns the unique string identifier of the app function to be executed.
+ *
+ * <p>When there is a package change or the device starts up, the metadata of available
+ * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code
+ * AppFunctionStaticMetadata} document.
+ *
+ * <p>The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from
+ * AppSearch.
+ *
+ * <p>If the {@code functionId} provided is invalid, the caller will get an invalid argument
+ * response.
+ */
@NonNull
public String getFunctionIdentifier() {
return mFunctionIdentifier;
@@ -83,6 +93,12 @@ public final class ExecuteAppFunctionRequest {
*
* <p>The bundle 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
+ * metadata will contain enough information for the caller to resolve the required parameters
+ * either using information from the metadata itself or using the AppFunction SDK for function
+ * callers.
*/
@NonNull
public GenericDocument getParameters() {
@@ -128,10 +144,7 @@ public final class ExecuteAppFunctionRequest {
@NonNull
public ExecuteAppFunctionRequest build() {
return new ExecuteAppFunctionRequest(
- mTargetPackageName,
- mFunctionIdentifier,
- mExtras,
- mParameters);
+ mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
}
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index 969e5d58b909..4e88fb025a9d 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -50,37 +50,102 @@ public final class ExecuteAppFunctionResponse {
*/
public static final String PROPERTY_RETURN_VALUE = "returnValue";
- /** The call was successful. */
+ /**
+ * 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. */
- public static final int RESULT_DENIED = 1;
+ /**
+ * 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;
- /** An unknown error occurred while processing the call in the AppFunctionService. */
- public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+ /**
+ * 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;
/**
- * An internal error occurred within AppFunctionManagerService.
+ * The caller tried to execute a disabled app function.
*
- * <p>This error may be considered similar to {@link IllegalStateException}
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
*/
- public static final int RESULT_INTERNAL_ERROR = 3;
+ public static final int RESULT_DISABLED = 1002;
/**
- * The caller supplied invalid arguments to the call.
+ * The caller tried to execute a function that does not exist.
*
- * <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 = 4;
+ public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
- /** The caller tried to execute a disabled app function. */
- public static final int RESULT_DISABLED = 5;
+ /**
+ * 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 RESULT_CANCELLED = 6;
+ 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;
@@ -170,6 +235,36 @@ public final class ExecuteAppFunctionResponse {
}
/**
+ * 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.
@@ -237,11 +332,28 @@ public final class ExecuteAppFunctionResponse {
RESULT_OK,
RESULT_DENIED,
RESULT_APP_UNKNOWN_ERROR,
- RESULT_INTERNAL_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/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
index 1f9fddd3c1ec..264f84209caf 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
@@ -105,7 +105,7 @@ class SidecarConverterTest {
val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
val platformResponse =
ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
null,
null
)
@@ -119,7 +119,7 @@ class SidecarConverterTest {
assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
assertThat(sidecarResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+ .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
assertThat(sidecarResponse.errorMessage).isNull()
}
@@ -152,7 +152,7 @@ class SidecarConverterTest {
val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
val sidecarResponse =
com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
null,
null
)
@@ -166,7 +166,7 @@ class SidecarConverterTest {
assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
assertThat(platformResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+ .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
assertThat(platformResponse.errorMessage).isNull()
}
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 266c23631800..fcb7efc35c94 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -384,6 +384,7 @@ cc_defaults {
"jni/ScopedParcel.cpp",
"jni/Shader.cpp",
"jni/RenderEffect.cpp",
+ "jni/RuntimeEffectUtils.cpp",
"jni/Typeface.cpp",
"jni/Utils.cpp",
"jni/YuvToJpegEncoder.cpp",
@@ -579,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/ColorFilter.h b/libs/hwui/ColorFilter.h
index 3a3bfb47dfdd..f6b6be08997a 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -22,6 +22,7 @@
#include <memory>
#include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
#include "SkColorFilter.h"
namespace android {
@@ -113,6 +114,36 @@ private:
std::vector<float> mMatrix;
};
+class RuntimeColorFilter : public ColorFilter {
+public:
+ RuntimeColorFilter(SkRuntimeEffectBuilder* builder) : mBuilder(builder) {}
+
+ void updateUniforms(JNIEnv* env, const char* name, const float vals[], int count,
+ bool isColor) {
+ UpdateFloatUniforms(env, mBuilder, name, vals, count, isColor);
+ discardInstance();
+ }
+
+ void updateUniforms(JNIEnv* env, const char* name, const int vals[], int count) {
+ UpdateIntUniforms(env, mBuilder, name, vals, count);
+ discardInstance();
+ }
+
+ void updateChild(JNIEnv* env, const char* name, SkFlattenable* childEffect) {
+ UpdateChild(env, mBuilder, name, childEffect);
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override {
+ // TODO: throw error if null
+ return mBuilder->makeColorFilter();
+ }
+
+private:
+ SkRuntimeEffectBuilder* mBuilder;
+};
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 93df47853769..5ad788c67816 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -139,3 +139,18 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "bitmap_ashmem_long_name"
+ namespace: "core_graphics"
+ description: "Whether to have more information in ashmem filenames for bitmaps"
+ bug: "369619160"
+}
+
+flag {
+ name: "animated_image_drawable_filter_bitmap"
+ is_exported: true
+ namespace: "core_graphics"
+ description: "API's that enable animated image drawables to use nearest sampling when scaling."
+ bug: "370523334"
+}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 69613c7d17cb..5e379aad9326 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -347,4 +347,26 @@ int AnimatedImageDrawable::currentFrameDuration() {
return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
}
+bool AnimatedImageDrawable::getFilterBitmap() const {
+ const SkFilterMode kFilterBitmap = mSkAnimatedImage->getFilterMode();
+ if (kFilterBitmap == SkFilterMode::kLinear) {
+ return true;
+ }
+ return false;
+}
+
+bool AnimatedImageDrawable::setFilterBitmap(bool filterBitmap) {
+ if (filterBitmap) {
+ if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kLinear) {
+ return false;
+ }
+ mSkAnimatedImage->setFilterMode(SkFilterMode::kLinear);
+ } else {
+ if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kNearest) {
+ return false;
+ }
+ mSkAnimatedImage->setFilterMode(SkFilterMode::kNearest);
+ }
+ return true;
+}
} // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 1e965abc82b5..22123249b7d6 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -87,6 +87,11 @@ public:
bool isRunning();
int getRepetitionCount() const { return mSkAnimatedImage->getRepetitionCount(); }
void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }
+ // Returns true if the filter mode is set to linear sampling; false if it is
+ // set to nearest neighbor sampling.
+ bool getFilterBitmap() const;
+ // Returns true if the filter mode was changed; false otherwise.
+ bool setFilterBitmap(bool filterBitmap);
void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
mEndListener = std::move(listener);
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index b73380e38fd1..cc292d9de7b2 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -15,6 +15,7 @@
*/
#include "Bitmap.h"
+#include <android-base/file.h>
#include "HardwareBitmapUploader.h"
#include "Properties.h"
#ifdef __ANDROID__ // Layoutlib does not support render thread
@@ -46,16 +47,27 @@
#include <SkImage.h>
#include <SkImageAndroid.h>
#include <SkImagePriv.h>
+#include <SkJpegEncoder.h>
#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
+#include <SkPngEncoder.h>
#include <SkRect.h>
#include <SkStream.h>
-#include <SkJpegEncoder.h>
-#include <SkPngEncoder.h>
#include <SkWebpEncoder.h>
+#include <atomic>
+#include <format>
#include <limits>
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool bitmap_ashmem_long_name() { return false; }
+}
+#endif
+
namespace android {
#ifdef __ANDROID__
@@ -86,6 +98,28 @@ static uint64_t AHardwareBuffer_getAllocationSize(AHardwareBuffer* aHardwareBuff
}
#endif
+// generate an ID for this Bitmap, id is a 64-bit integer of 3 parts:
+// 0000xxxxxx - the lower 6 decimal digits is a monotonically increasing number
+// 000x000000 - the 7th decimal digit is the storage type (see PixelStorageType)
+// xxx0000000 - the 8th decimal digit and above is the current pid
+//
+// e.g. 43231000076 - means this bitmap is the 76th bitmap created, has the
+// storage type of 'Heap', and is created in a process with pid 4323.
+//
+// NOTE:
+// 1) the monotonic number could increase beyond 1000,000 and wrap around, which
+// only happens when more than 1,000,000 bitmaps have been created over time.
+// This could result in two IDs being the same despite being really rare.
+// 2) the IDs are intentionally represented in decimal to make it easier to
+// reason and associate with numbers shown in heap dump (mostly in decimal)
+// and PIDs shown in different tools (mostly in decimal as well).
+uint64_t Bitmap::getId(PixelStorageType type) {
+ static std::atomic<uint64_t> idCounter{0};
+ return (idCounter.fetch_add(1) % 1000000)
+ + static_cast<uint64_t>(type) * 1000000
+ + static_cast<uint64_t>(getpid()) * 10000000;
+}
+
bool Bitmap::computeAllocationSize(size_t rowBytes, int height, size_t* size) {
return 0 <= height && height <= std::numeric_limits<size_t>::max() &&
!__builtin_mul_overflow(rowBytes, (size_t)height, size) &&
@@ -117,6 +151,20 @@ static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
return wrapper;
}
+std::string Bitmap::getAshmemId(const char* tag, uint64_t bitmapId,
+ int width, int height, size_t size) {
+ if (!hwui_flags::bitmap_ashmem_long_name()) {
+ return "bitmap";
+ }
+ static std::string sCmdline = [] {
+ std::string temp;
+ android::base::ReadFileToString("/proc/self/cmdline", &temp);
+ return temp;
+ }();
+ return std::format("bitmap/{}-id_{}-{}x{}-size_{}-{}",
+ tag, bitmapId, width, height, size, sCmdline);
+}
+
sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) {
return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap);
}
@@ -124,7 +172,9 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) {
sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
#ifdef __ANDROID__
// Create new ashmem region with read/write priv
- int fd = ashmem_create_region("bitmap", size);
+ uint64_t id = getId(PixelStorageType::Ashmem);
+ auto ashmemId = getAshmemId("allocate", id, info.width(), info.height(), size);
+ int fd = ashmem_create_region(ashmemId.c_str(), size);
if (fd < 0) {
return nullptr;
}
@@ -140,7 +190,7 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info,
close(fd);
return nullptr;
}
- return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes));
+ return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, id));
#else
return Bitmap::allocateHeapBitmap(size, info, rowBytes);
#endif
@@ -261,7 +311,8 @@ void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes) {
Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes)
: SkPixelRef(info.width(), info.height(), address, rowBytes)
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::Heap) {
+ , mPixelStorageType(PixelStorageType::Heap)
+ , mId(getId(mPixelStorageType)) {
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
traceBitmapCreate();
@@ -270,16 +321,19 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy
Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info)
: SkPixelRef(info.width(), info.height(), pixelRef.pixels(), pixelRef.rowBytes())
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::WrappedPixelRef) {
+ , mPixelStorageType(PixelStorageType::WrappedPixelRef)
+ , mId(getId(mPixelStorageType)) {
pixelRef.ref();
mPixelStorage.wrapped.pixelRef = &pixelRef;
traceBitmapCreate();
}
-Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes)
+Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
+ size_t rowBytes, uint64_t id)
: SkPixelRef(info.width(), info.height(), address, rowBytes)
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::Ashmem) {
+ , mPixelStorageType(PixelStorageType::Ashmem)
+ , mId(id != INVALID_BITMAP_ID ? id : getId(mPixelStorageType)) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
@@ -293,7 +347,8 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes
, mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Hardware)
, mPalette(palette)
- , mPaletteGenerationId(getGenerationID()) {
+ , mPaletteGenerationId(getGenerationID())
+ , mId(getId(mPixelStorageType)) {
mPixelStorage.hardware.buffer = buffer;
mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
AHardwareBuffer_acquire(buffer);
@@ -578,6 +633,7 @@ void Bitmap::setGainmap(sp<uirenderer::Gainmap>&& gainmap) {
}
std::mutex Bitmap::mLock{};
+
size_t Bitmap::mTotalBitmapBytes = 0;
size_t Bitmap::mTotalBitmapCount = 0;
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 3d55d859ed5f..8abe6a8c445a 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -37,10 +37,10 @@ class SkWStream;
namespace android {
enum class PixelStorageType {
- WrappedPixelRef,
- Heap,
- Ashmem,
- Hardware,
+ WrappedPixelRef = 0,
+ Heap = 1,
+ Ashmem = 2,
+ Hardware = 3,
};
// TODO: Find a better home for this. It's here because hwui/Bitmap is exported and CanvasTransform
@@ -79,6 +79,9 @@ public:
static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info);
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
+ static std::string getAshmemId(const char* tag, uint64_t bitmapId,
+ int width, int height, size_t size);
+
/* The createFrom factories construct a new Bitmap object by wrapping the already allocated
* memory that is provided as an input param.
*/
@@ -104,6 +107,10 @@ public:
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
void setAlphaType(SkAlphaType alphaType);
+ uint64_t getId() const {
+ return mId;
+ }
+
void getSkBitmap(SkBitmap* outBitmap);
SkBitmap getSkBitmap() {
@@ -177,11 +184,14 @@ public:
static bool compress(const SkBitmap& bitmap, JavaCompressFormat format,
int32_t quality, SkWStream* stream);
private:
+ static constexpr uint64_t INVALID_BITMAP_ID = 0u;
+
static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes);
Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info);
- Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes);
+ Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes,
+ uint64_t id = INVALID_BITMAP_ID);
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes,
BitmapPalette palette);
@@ -229,6 +239,9 @@ private:
sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
+ uint64_t mId; // unique ID for this bitmap
+ static uint64_t getId(PixelStorageType type);
+
// for tracing total number and memory usage of bitmaps
static std::mutex mLock;
static size_t mTotalBitmapBytes;
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/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index b01e38d014a9..2c8530d4daeb 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -276,6 +276,18 @@ static void AnimatedImageDrawable_nSetBounds(JNIEnv* env, jobject /*clazz*/, jlo
drawable->setStagingBounds(rect);
}
+static jboolean AnimatedImageDrawable_nSetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+ jlong nativePtr, jboolean filterBitmap) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->setFilterBitmap(filterBitmap);
+}
+
+static jboolean AnimatedImageDrawable_nGetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+ jlong nativePtr) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ return drawable->getFilterBitmap();
+}
+
static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{"nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",
(void*)AnimatedImageDrawable_nCreate},
@@ -294,6 +306,8 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{"nNativeByteSize", "(J)J", (void*)AnimatedImageDrawable_nNativeByteSize},
{"nSetMirrored", "(JZ)V", (void*)AnimatedImageDrawable_nSetMirrored},
{"nSetBounds", "(JLandroid/graphics/Rect;)V", (void*)AnimatedImageDrawable_nSetBounds},
+ {"nSetFilterBitmap", "(JZ)Z", (void*)AnimatedImageDrawable_nSetFilterBitmap},
+ {"nGetFilterBitmap", "(J)Z", (void*)AnimatedImageDrawable_nGetFilterBitmap},
};
int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 010c4e8dfb3a..29efd98b41d0 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -196,7 +196,7 @@ jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int density) {
static jmethodID gBitmap_constructorMethodID =
GetMethodIDOrDie(env, gBitmap_class,
- "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+ "<init>", "(JJIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
@@ -209,7 +209,8 @@ jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
bitmapWrapper->bitmap().setImmutable();
}
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
+ static_cast<jlong>(bitmap->getId()), reinterpret_cast<jlong>(bitmapWrapper),
+ bitmap->width(), bitmap->height(), density,
isPremultiplied, ninePatchChunk, ninePatchInsets, fromMalloc);
if (env->ExceptionCheck() != 0) {
@@ -668,14 +669,20 @@ static binder_status_t writeBlobFromFd(AParcel* parcel, int32_t size, int fd) {
return STATUS_OK;
}
-static binder_status_t writeBlob(AParcel* parcel, const int32_t size, const void* data, bool immutable) {
+static binder_status_t writeBlob(AParcel* parcel, uint64_t bitmapId, const SkBitmap& bitmap) {
+ const size_t size = bitmap.computeByteSize();
+ const void* data = bitmap.getPixels();
+ const bool immutable = bitmap.isImmutable();
+
if (size <= 0 || data == nullptr) {
return STATUS_NOT_ENOUGH_DATA;
}
binder_status_t error = STATUS_OK;
if (shouldUseAshmem(parcel, size)) {
// Create new ashmem region with read/write priv
- base::unique_fd fd(ashmem_create_region("bitmap", size));
+ auto ashmemId = Bitmap::getAshmemId("writeblob", bitmapId,
+ bitmap.width(), bitmap.height(), size);
+ base::unique_fd fd(ashmem_create_region(ashmemId.c_str(), size));
if (fd.get() < 0) {
return STATUS_NO_MEMORY;
}
@@ -883,8 +890,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
p.allowFds() ? "allowed" : "forbidden");
#endif
- size_t size = bitmap.computeByteSize();
- status = writeBlob(p.get(), size, bitmap.getPixels(), bitmap.isImmutable());
+ status = writeBlob(p.get(), bitmapWrapper->bitmap().getId(), bitmap);
if (status) {
doThrowRE(env, "Could not copy bitmap to parcel blob.");
return JNI_FALSE;
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..5ffd5b9016d8 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>
@@ -376,6 +377,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 +394,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/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 0b95148d3c82..20301d2c76ec 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -18,7 +18,9 @@
#include "ColorFilter.h"
#include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
#include "SkBlendMode.h"
+#include "include/effects/SkRuntimeEffect.h"
namespace android {
@@ -89,6 +91,78 @@ public:
filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
}
}
+
+ static jlong RuntimeColorFilter_createColorFilter(JNIEnv* env, jobject, jstring agsl) {
+ ScopedUtfChars strSksl(env, agsl);
+ auto result = SkRuntimeEffect::MakeForColorFilter(SkString(strSksl.c_str()),
+ SkRuntimeEffect::Options{});
+ if (result.effect.get() == nullptr) {
+ doThrowIAE(env, result.errorText.c_str());
+ return 0;
+ }
+ auto builder = new SkRuntimeEffectBuilder(std::move(result.effect));
+ auto* runtimeColorFilter = new RuntimeColorFilter(builder);
+ runtimeColorFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(runtimeColorFilter));
+ }
+
+ static void RuntimeColorFilter_updateUniformsFloatArray(JNIEnv* env, jobject,
+ jlong colorFilterPtr,
+ jstring uniformName,
+ jfloatArray uniforms,
+ jboolean isColor) {
+ auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
+ if (filter) {
+ filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length(),
+ isColor);
+ }
+ }
+
+ static void RuntimeColorFilter_updateUniformsFloats(JNIEnv* env, jobject, jlong colorFilterPtr,
+ jstring uniformName, jfloat value1,
+ jfloat value2, jfloat value3, jfloat value4,
+ jint count) {
+ auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+ ScopedUtfChars name(env, uniformName);
+ const float values[4] = {value1, value2, value3, value4};
+ if (filter) {
+ filter->updateUniforms(env, name.c_str(), values, count, false);
+ }
+ }
+
+ static void RuntimeColorFilter_updateUniformsIntArray(JNIEnv* env, jobject,
+ jlong colorFilterPtr, jstring uniformName,
+ jintArray uniforms) {
+ auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+ ScopedUtfChars name(env, uniformName);
+ AutoJavaIntArray autoValues(env, uniforms, 0);
+ if (filter) {
+ filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length());
+ }
+ }
+
+ static void RuntimeColorFilter_updateUniformsInts(JNIEnv* env, jobject, jlong colorFilterPtr,
+ jstring uniformName, jint value1, jint value2,
+ jint value3, jint value4, jint count) {
+ auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+ ScopedUtfChars name(env, uniformName);
+ const int values[4] = {value1, value2, value3, value4};
+ if (filter) {
+ filter->updateUniforms(env, name.c_str(), values, count);
+ }
+ }
+
+ static void RuntimeColorFilter_updateChild(JNIEnv* env, jobject, jlong colorFilterPtr,
+ jstring childName, jlong childPtr) {
+ auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+ ScopedUtfChars name(env, childName);
+ auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
+ if (filter && child) {
+ filter->updateChild(env, name.c_str(), child);
+ }
+ }
};
static const JNINativeMethod colorfilter_methods[] = {
@@ -107,6 +181,20 @@ static const JNINativeMethod colormatrix_methods[] = {
{"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
{"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
+static const JNINativeMethod runtime_color_filter_methods[] = {
+ {"nativeCreateRuntimeColorFilter", "(Ljava/lang/String;)J",
+ (void*)ColorFilterGlue::RuntimeColorFilter_createColorFilter},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+ (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloatArray},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+ (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloats},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+ (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsIntArray},
+ {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+ (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
+ {"nativeUpdateChild", "(JLjava/lang/String;J)V",
+ (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};
+
int register_android_graphics_ColorFilter(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
NELEM(colorfilter_methods));
@@ -118,7 +206,10 @@ int register_android_graphics_ColorFilter(JNIEnv* env) {
NELEM(lighting_methods));
android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
colormatrix_methods, NELEM(colormatrix_methods));
-
+ android::RegisterMethodsOrDie(env, "android/graphics/RuntimeColorFilter",
+ runtime_color_filter_methods,
+ NELEM(runtime_color_filter_methods));
+
return 0;
}
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/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
new file mode 100644
index 000000000000..46db8633c66e
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#include "RuntimeEffectUtils.h"
+
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+ va_end(args);
+ return ret;
+}
+
+bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+ switch (type) {
+ case SkRuntimeEffect::Uniform::Type::kFloat:
+ case SkRuntimeEffect::Uniform::Type::kFloat2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4:
+ case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+ case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+ case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+ return false;
+ case SkRuntimeEffect::Uniform::Type::kInt:
+ case SkRuntimeEffect::Uniform::Type::kInt2:
+ case SkRuntimeEffect::Uniform::Type::kInt3:
+ case SkRuntimeEffect::Uniform::Type::kInt4:
+ return true;
+ }
+}
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+ const float values[], int count, bool isColor) {
+ SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+ if (isColor) {
+ jniThrowExceptionFmt(
+ env, "java/lang/IllegalArgumentException",
+ "attempting to set a color uniform using the non-color specific APIs: %s %x",
+ uniformName, uniform.fVar->flags);
+ } else {
+ ThrowIAEFmt(env,
+ "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+ uniformName);
+ }
+ } else if (isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<float>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+ const int values[], int count) {
+ SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+ if (uniform.fVar == nullptr) {
+ ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+ } else if (!isIntUniformType(uniform.fVar->type)) {
+ ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+ uniformName);
+ } else if (!uniform.set<int>(values, count)) {
+ ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+ uniform.fVar->sizeInBytes(), sizeof(float) * count);
+ }
+}
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+ SkFlattenable* childEffect) {
+ SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
+ if (builderChild.fChild == nullptr) {
+ ThrowIAEFmt(env, "unable to find shader named %s", childName);
+ return;
+ }
+
+ builderChild = sk_ref_sp(childEffect);
+}
+
+} // namespace uirenderer
+} // namespace android \ No newline at end of file
diff --git a/libs/hwui/jni/RuntimeEffectUtils.h b/libs/hwui/jni/RuntimeEffectUtils.h
new file mode 100644
index 000000000000..75623c0f7ac1
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef RUNTIMEEFFECTUTILS_H
+#define RUNTIMEEFFECTUTILS_H
+
+#include "GraphicsJNI.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+ const float values[], int count, bool isColor);
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+ const int values[], int count);
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+ SkFlattenable* childEffect);
+} // namespace uirenderer
+} // namespace android
+
+#endif // MAIN_RUNTIMEEFFECTUTILS_H
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 6e03bbd0fa16..d9dc8eb8c1b1 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -593,9 +593,9 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
Matrix4 transform;
SkIRect clipBounds;
+ uirenderer::Rect initialClipBounds;
+ const auto clipFlags = props.getClippingFlags();
if (enableClip) {
- uirenderer::Rect initialClipBounds;
- const auto clipFlags = props.getClippingFlags();
if (clipFlags) {
props.getClippingRectForFlags(clipFlags, &initialClipBounds);
} else {
@@ -659,8 +659,8 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
- static_cast<jint>(clipBounds.fRight),
- static_cast<jint>(clipBounds.fBottom));
+ static_cast<jint>(clipBounds.fRight), static_cast<jint>(clipBounds.fBottom),
+ static_cast<jint>(props.getWidth()), static_cast<jint>(props.getHeight()));
}
if (!keepListening) {
env->DeleteGlobalRef(mListener);
@@ -891,7 +891,7 @@ int register_android_view_RenderNode(JNIEnv* env) {
gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
- env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
+ env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIIIII)Z");
gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
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/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
deleted file mode 120000
index 4fb4784f9f60..000000000000
--- a/libs/hwui/platform/host/android/api-level.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../bionic/libc/include/android/api-level.h \ No newline at end of file
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