summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java59
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java23
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java5
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java224
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java79
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java309
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java267
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java9
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java20
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java8
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java88
-rw-r--r--libs/WindowManager/Shell/Android.bp9
-rw-r--r--libs/WindowManager/Shell/OWNERS2
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig10
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp34
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml1
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.pngbin0 -> 56741 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.pngbin0 -> 56741 bytes
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties1
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml29
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml28
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml19
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt154
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java)33
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt204
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java1
-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/phone/PipController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java98
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java132
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java101
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java516
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt484
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt9
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt68
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt375
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt456
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java74
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt126
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt107
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt36
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt83
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java55
-rw-r--r--libs/androidfw/StringPool.cpp28
-rw-r--r--libs/androidfw/include/androidfw/StringPool.h5
-rw-r--r--libs/hwui/Android.bp10
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp21
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h13
-rw-r--r--libs/hwui/jni/MeshSpecification.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp2
170 files changed, 4748 insertions, 2728 deletions
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 822a07c23950..544f0f38f48c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -894,9 +894,7 @@ class DividerPresenter implements View.OnTouchListener {
private static boolean isDraggingToFullscreenAllowed(
@NonNull DividerAttributes dividerAttributes) {
- // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
- // updated to v7.
- return false;
+ return dividerAttributes.isDraggingToFullscreenAllowed();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index f9a6caf42e6e..1eb95c1efb08 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -29,6 +30,7 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+import android.annotation.ColorInt;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -48,6 +50,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -67,10 +70,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
@NonNull
private final TaskFragmentCallback mCallback;
- @VisibleForTesting
- @Nullable
- TaskFragmentAnimationController mAnimationController;
-
/**
* Callback that notifies the controller about changes to task fragments.
*/
@@ -88,25 +87,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
mCallback = callback;
}
- @Override
- public void unregisterOrganizer() {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations();
- mAnimationController = null;
- }
- super.unregisterOrganizer();
- }
-
- /**
- * Overrides the animation for transitions of embedded activities organized by this organizer.
- */
- void overrideSplitAnimation() {
- if (mAnimationController == null) {
- mAnimationController = new TaskFragmentAnimationController(this);
- }
- mAnimationController.registerRemoteAnimations();
- }
-
/**
* Starts a new Activity and puts it into split with an existing Activity side-by-side.
* @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will
@@ -391,13 +371,34 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
- final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ final TaskFragmentAnimationParams.Builder builder =
+ new TaskFragmentAnimationParams.Builder();
+ final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
+ builder.setAnimationBackgroundColor(animationBackgroundColor);
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ final int openAnimationResId =
+ splitAttributes.getAnimationParams().getOpenAnimationResId();
+ builder.setOpenAnimationResId(openAnimationResId);
+ final int closeAnimationResId =
+ splitAttributes.getAnimationParams().getCloseAnimationResId();
+ builder.setCloseAnimationResId(closeAnimationResId);
+ final int changeAnimationResId =
+ splitAttributes.getAnimationParams().getChangeAnimationResId();
+ builder.setChangeAnimationResId(changeAnimationResId);
+ }
+ return builder.build();
+ }
+
+ @ColorInt
+ private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
+ int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
+ AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ animationBackground = splitAttributes.getAnimationParams().getAnimationBackground();
+ }
if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
- return new TaskFragmentAnimationParams.Builder()
- .setAnimationBackgroundColor(colorBackground.getColor())
- .build();
- } else {
- return TaskFragmentAnimationParams.DEFAULT;
+ animationBackgroundColor = colorBackground.getColor();
}
+ return animationBackgroundColor;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 7ddda1f98809..b12072373c5d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -114,7 +114,6 @@ import java.util.function.BiConsumer;
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
static final String TAG = "SplitController";
- static final boolean ENABLE_SHELL_TRANSITIONS = true;
// TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
// association. It's not set in WM Extensions nor Wm Jetpack library currently.
@@ -205,11 +204,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Listener registered to {@link ClientTransactionListenerController}. */
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
- Flags.activityWindowInfoFlag()
- ? this::onActivityWindowInfoChanged
- : null;
+ this::onActivityWindowInfoChanged;
private final Handler mHandler;
private final MainThreadExecutor mExecutor;
@@ -3097,20 +3094,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
public boolean isActivityEmbedded(@NonNull Activity activity) {
Objects.requireNonNull(activity);
synchronized (mLock) {
- if (Flags.activityWindowInfoFlag()) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
- }
- return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
@Override
public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
@NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
synchronized (mLock) {
@@ -3124,9 +3115,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void clearEmbeddedActivityWindowInfoCallback() {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
if (mEmbeddedActivityWindowInfoCallback == null) {
return;
@@ -3147,9 +3135,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
@Override
public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
- if (!Flags.activityWindowInfoFlag()) {
- return null;
- }
synchronized (mLock) {
final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
return activityWindowInfo != null
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index eb1fc23d6b00..ea60b1531a3f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -166,11 +166,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
mWindowLayoutComponent = windowLayoutComponent;
mController = controller;
registerOrganizer();
- if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell when Shell transition is enabled.
- overrideSplitAnimation();
- }
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
deleted file mode 100644
index 33220c44a3b5..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.graphics.Matrix.MTRANS_X;
-import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Choreographer;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import androidx.annotation.NonNull;
-
-/**
- * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
- *
- * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
- */
-class TaskFragmentAnimationAdapter {
-
- /**
- * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
- */
- private static final int LAYER_NO_OVERRIDE = -1;
-
- @NonNull
- final Animation mAnimation;
- @NonNull
- final RemoteAnimationTarget mTarget;
- @NonNull
- final SurfaceControl mLeash;
- /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
- @NonNull
- private final Rect mWholeAnimationBounds = new Rect();
- /**
- * Area in absolute coordinate that should represent all the content to show for this window.
- * This should be the end bounds for opening window, and start bounds for closing window in case
- * the window is resizing during the open/close transition.
- */
- @NonNull
- private final Rect mContentBounds = new Rect();
- /** Offset relative to the window parent surface for {@link #mContentBounds}. */
- @NonNull
- private final Point mContentRelOffset = new Point();
-
- @NonNull
- final Transformation mTransformation = new Transformation();
- @NonNull
- final float[] mMatrix = new float[9];
- @NonNull
- final float[] mVecs = new float[4];
- @NonNull
- final Rect mRect = new Rect();
- private boolean mIsFirstFrame = true;
- private int mOverrideLayer = LAYER_NO_OVERRIDE;
-
- TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target) {
- this(animation, target, target.leash, target.screenSpaceBounds);
- }
-
- /**
- * @param leash the surface to animate.
- * @param wholeAnimationBounds area in absolute coordinate that the animation surface shouldn't
- * go beyond.
- */
- TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
- @NonNull Rect wholeAnimationBounds) {
- mAnimation = animation;
- mTarget = target;
- mLeash = leash;
- mWholeAnimationBounds.set(wholeAnimationBounds);
- if (target.mode == MODE_CLOSING) {
- // When it is closing, we want to show the content at the start position in case the
- // window is resizing as well. For example, when the activities is changing from split
- // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
- final Rect startBounds = target.startBounds;
- final Rect endBounds = target.screenSpaceBounds;
- mContentBounds.set(startBounds);
- mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
- mContentRelOffset.offset(
- startBounds.left - endBounds.left,
- startBounds.top - endBounds.top);
- } else {
- mContentBounds.set(target.screenSpaceBounds);
- mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
- }
- }
-
- /**
- * Surface layer to be set at the first frame of the animation. We will not set the layer if it
- * is set to {@link #LAYER_NO_OVERRIDE}.
- */
- final void overrideLayer(int layer) {
- mOverrideLayer = layer;
- }
-
- /** Called on frame update. */
- final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
- if (mIsFirstFrame) {
- t.show(mLeash);
- if (mOverrideLayer != LAYER_NO_OVERRIDE) {
- t.setLayer(mLeash, mOverrideLayer);
- }
- mIsFirstFrame = false;
- }
-
- // Extract the transformation to the current time.
- mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
- mTransformation);
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- onAnimationUpdateInner(t);
- }
-
- /** To be overridden by subclasses to adjust the animation surface change. */
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- // Update the surface position and alpha.
- mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
-
- // Get current surface bounds in absolute coordinate.
- // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
- final int positionX = Math.round(mMatrix[MTRANS_X]);
- final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mContentBounds);
- cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
-
- // Store the current offset of the surface top left from (0,0) in absolute coordinate.
- final int offsetX = cropRect.left;
- final int offsetY = cropRect.top;
-
- // Intersect to make sure the animation happens within the whole animation bounds.
- if (!cropRect.intersect(mWholeAnimationBounds)) {
- // Hide the surface when it is outside of the animation area.
- t.setAlpha(mLeash, 0);
- }
-
- // cropRect is in absolute coordinate, so we need to translate it to surface top left.
- cropRect.offset(-offsetX, -offsetY);
- t.setCrop(mLeash, cropRect);
- }
-
- /** Called after animation finished. */
- final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
- onAnimationUpdate(t, mAnimation.getDuration());
- }
-
- final long getDurationHint() {
- return mAnimation.computeDurationHint();
- }
-
- /**
- * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
- * size change.
- */
- static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
-
- SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
- // Start leash is the snapshot of the starting surface.
- super(animation, target, target.startLeash, target.screenSpaceBounds);
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- // Snapshot should always be placed at the top left of the animation leash.
- mTransformation.getMatrix().postTranslate(0, 0);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
- }
- }
-
- /**
- * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
- */
- static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
-
- BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
- super(animation, target);
- }
-
- @Override
- void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
- mTransformation.getMatrix().postTranslate(
- mTarget.localBounds.left, mTarget.localBounds.top);
- t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
- t.setAlpha(mLeash, mTransformation.getAlpha());
-
- // The following applies an inverse scale to the clip-rect so that it crops "after" the
- // scale instead of before.
- mVecs[1] = mVecs[2] = 0;
- mVecs[0] = mVecs[3] = 1;
- mTransformation.getMatrix().mapVectors(mVecs);
- mVecs[0] = 1.f / mVecs[0];
- mVecs[3] = 1.f / mVecs[3];
- final Rect clipRect = mTransformation.getClipRect();
- mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
- mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
- mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
- mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
- t.setWindowCrop(mLeash, mRect);
- }
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
deleted file mode 100644
index d7eb9a01f57c..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-
-import android.util.Log;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/** Controls the TaskFragment remote animations. */
-class TaskFragmentAnimationController {
-
- private static final String TAG = "TaskFragAnimationCtrl";
- static final boolean DEBUG = false;
-
- private final TaskFragmentOrganizer mOrganizer;
- private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
- @VisibleForTesting
- final RemoteAnimationDefinition mDefinition;
- private boolean mIsRegistered;
-
- TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
- mOrganizer = organizer;
- mDefinition = new RemoteAnimationDefinition();
- final RemoteAnimationAdapter animationAdapter =
- new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
- }
-
- void registerRemoteAnimations() {
- if (DEBUG) {
- Log.v(TAG, "registerRemoteAnimations");
- }
- if (mIsRegistered) {
- return;
- }
- mOrganizer.registerRemoteAnimations(mDefinition);
- mIsRegistered = true;
- }
-
- void unregisterRemoteAnimations() {
- if (DEBUG) {
- Log.v(TAG, "unregisterRemoteAnimations");
- }
- if (!mIsRegistered) {
- return;
- }
- mOrganizer.unregisterRemoteAnimations();
- mIsRegistered = false;
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
deleted file mode 100644
index d9b73a8290f5..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_CHANGING;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.view.animation.Animation;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiFunction;
-
-/** To run the TaskFragment animations. */
-class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
-
- private static final String TAG = "TaskFragAnimationRunner";
- private final Handler mHandler;
- private final TaskFragmentAnimationSpec mAnimationSpec;
-
- TaskFragmentAnimationRunner() {
- HandlerThread animationThread = new HandlerThread(
- "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
- animationThread.start();
- mHandler = animationThread.getThreadHandler();
- mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
- }
-
- @Nullable
- private Animator mAnimator;
-
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] apps,
- @NonNull RemoteAnimationTarget[] wallpapers,
- @NonNull RemoteAnimationTarget[] nonApps,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- if (wallpapers.length != 0 || nonApps.length != 0) {
- throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
- + "wallpaper or non-app windows.");
- }
- if (TaskFragmentAnimationController.DEBUG) {
- Log.v(TAG, "onAnimationStart transit=" + transit);
- }
- mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
- }
-
- @Override
- public void onAnimationCancelled() {
- mHandler.post(this::cancelAnimation);
- }
-
- /** Creates and starts animation. */
- private void startAnimation(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- if (mAnimator != null) {
- Log.w(TAG, "start new animation when the previous one is not finished yet.");
- mAnimator.cancel();
- }
- mAnimator = createAnimator(transit, targets, finishedCallback);
- mAnimator.start();
- }
-
- /** Cancels animation. */
- private void cancelAnimation() {
- if (mAnimator == null) {
- return;
- }
- mAnimator.cancel();
- mAnimator = null;
- }
-
- /** Creates the animator given the transition type and windows. */
- @NonNull
- private Animator createAnimator(@WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets,
- @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
- final List<TaskFragmentAnimationAdapter> adapters =
- createAnimationAdapters(transit, targets);
- long duration = 0;
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- duration = Math.max(duration, adapter.getDurationHint());
- }
- final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(duration);
- animator.addUpdateListener((anim) -> {
- // Update all adapters in the same transaction.
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
- }
- t.apply();
- });
- animator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {}
-
- @Override
- public void onAnimationEnd(Animator animation) {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (TaskFragmentAnimationAdapter adapter : adapters) {
- adapter.onAnimationEnd(t);
- }
- t.apply();
-
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- mAnimator = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {}
-
- @Override
- public void onAnimationRepeat(Animator animation) {}
- });
- return animator;
- }
-
- /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
- @NonNull
- private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
- @WindowManager.TransitionOldType int transit,
- @NonNull RemoteAnimationTarget[] targets) {
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- return createOpenAnimationAdapters(targets);
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- return createCloseAnimationAdapters(targets);
- case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
- return createChangeAnimationAdapters(targets);
- default:
- throw new IllegalArgumentException("Unhandled transit type=" + transit);
- }
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
- mAnimationSpec::loadOpenAnimation);
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
- mAnimationSpec::loadCloseAnimation);
- }
-
- /**
- * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
- * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
- */
- @NonNull
- private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
- @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
- // We need to know if the target window is only a partial of the whole animation screen.
- // If so, we will need to adjust it to make the whole animation screen looks like one.
- final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
- final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
- final Rect openingWholeScreenBounds = new Rect();
- final Rect closingWholeScreenBounds = new Rect();
- for (RemoteAnimationTarget target : targets) {
- if (target.mode != MODE_CLOSING) {
- openingTargets.add(target);
- openingWholeScreenBounds.union(target.screenSpaceBounds);
- } else {
- closingTargets.add(target);
- closingWholeScreenBounds.union(target.screenSpaceBounds);
- // Union the start bounds since this may be the ClosingChanging animation.
- closingWholeScreenBounds.union(target.startBounds);
- }
- }
-
- // For OPEN transition, open windows should be above close windows.
- // For CLOSE transition, open windows should be below close windows.
- int offsetLayer = TYPE_LAYER_OFFSET;
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
- for (RemoteAnimationTarget target : openingTargets) {
- final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
- animationProvider, openingWholeScreenBounds);
- if (isOpening) {
- adapter.overrideLayer(offsetLayer++);
- }
- adapters.add(adapter);
- }
- for (RemoteAnimationTarget target : closingTargets) {
- final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
- animationProvider, closingWholeScreenBounds);
- if (!isOpening) {
- adapter.overrideLayer(offsetLayer++);
- }
- adapters.add(adapter);
- }
- return adapters;
- }
-
- @NonNull
- private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
- @NonNull RemoteAnimationTarget target,
- @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
- @NonNull Rect wholeAnimationBounds) {
- final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
- return new TaskFragmentAnimationAdapter(animation, target, target.leash,
- wholeAnimationBounds);
- }
-
- @NonNull
- private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
- @NonNull RemoteAnimationTarget[] targets) {
- if (shouldUseJumpCutForChangeAnimation(targets)) {
- return new ArrayList<>();
- }
-
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
- for (RemoteAnimationTarget target : targets) {
- if (target.mode == MODE_CHANGING) {
- // This is the target with bounds change.
- final Animation[] animations =
- mAnimationSpec.createChangeBoundsChangeAnimations(target);
- // Adapter for the starting snapshot leash.
- adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
- animations[0], target));
- // Adapter for the ending bounds changed leash.
- adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
- animations[1], target));
- continue;
- }
-
- // These are the other targets that don't have bounds change in the same transition.
- final Animation animation;
- if (target.hasAnimatingParent) {
- // No-op if it will be covered by the changing parent window.
- animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
- } else if (target.mode == MODE_CLOSING) {
- animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
- } else {
- animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
- }
- adapters.add(new TaskFragmentAnimationAdapter(animation, target));
- }
- return adapters;
- }
-
- /**
- * Whether we should use jump cut for the change transition.
- * This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
- * Uses a jump cut animation to simplify.
- */
- private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
- for (RemoteAnimationTarget target : targets) {
- if (target.hasAnimatingParent) {
- continue;
- }
- hasOpeningWindow |= target.mode == MODE_OPENING;
- hasClosingWindow |= target.mode == MODE_CLOSING;
- }
- return hasOpeningWindow && hasClosingWindow;
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
deleted file mode 100644
index 1f866c3b99c9..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-import com.android.internal.policy.AttributeCache;
-import com.android.internal.policy.TransitionAnimation;
-
-/** Animation spec for TaskFragment transition. */
-// TODO(b/206557124): provide an easier way to customize animation
-class TaskFragmentAnimationSpec {
-
- private static final String TAG = "TaskFragAnimationSpec";
- private static final int CHANGE_ANIMATION_DURATION = 517;
- private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
- private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
-
- private final Context mContext;
- private final TransitionAnimation mTransitionAnimation;
- private final Interpolator mFastOutExtraSlowInInterpolator;
- private final LinearInterpolator mLinearInterpolator;
- private float mTransitionAnimationScaleSetting;
-
- TaskFragmentAnimationSpec(@NonNull Handler handler) {
- mContext = ActivityThread.currentActivityThread().getApplication();
- mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
- // Initialize the AttributeCache for the TransitionAnimation.
- AttributeCache.init(mContext);
- mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.fast_out_extra_slow_in);
- mLinearInterpolator = new LinearInterpolator();
-
- // The transition animation should be adjusted based on the developer option.
- final ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
- new SettingsObserver(handler));
- }
-
- /** For target that doesn't need to be animated. */
- @NonNull
- static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
- // Noop but just keep the target showing/hiding.
- final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
- return new AlphaAnimation(alpha, alpha);
- }
-
- /** Animation for target that is opening in a change transition. */
- @NonNull
- Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- final Rect bounds = target.screenSpaceBounds;
- final int startLeft;
- final int startTop;
- if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated in from left or right depending on its position.
- startTop = 0;
- startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
- } else {
- // The window will be animated in from top or bottom depending on its position.
- startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
- startLeft = 0;
- }
-
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
- animation.setInterpolator(mFastOutExtraSlowInInterpolator);
- animation.setDuration(CHANGE_ANIMATION_DURATION);
- animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- /** Animation for target that is closing in a change transition. */
- @NonNull
- Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- // Use startBounds if the window is closing in case it may also resize.
- final Rect bounds = target.startBounds;
- final int endTop;
- final int endLeft;
- if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated out to left or right depending on its position.
- endTop = 0;
- endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
- } else {
- // The window will be animated out to top or bottom depending on its position.
- endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
- endLeft = 0;
- }
-
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
- animation.setInterpolator(mFastOutExtraSlowInInterpolator);
- animation.setDuration(CHANGE_ANIMATION_DURATION);
- animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- /**
- * Animation for target that is changing (bounds change) in a change transition.
- * @return the return array always has two elements. The first one is for the start leash, and
- * the second one is for the end leash.
- */
- @NonNull
- Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
- // Both start bounds and end bounds are in screen coordinates. We will post translate
- // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
- final Rect startBounds = target.startBounds;
- final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
- final Rect endBounds = target.screenSpaceBounds;
- float scaleX = ((float) startBounds.width()) / endBounds.width();
- float scaleY = ((float) startBounds.height()) / endBounds.height();
- // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
- // be scaled up with its parent.
- float startScaleX = 1.f / scaleX;
- float startScaleY = 1.f / scaleY;
-
- // The start leash will be fade out.
- final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
- final Animation startAlpha = new AlphaAnimation(1f, 0f);
- startAlpha.setInterpolator(mLinearInterpolator);
- startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
- startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
- startSet.addAnimation(startAlpha);
- final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
- startScaleY);
- startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
- startScale.setDuration(CHANGE_ANIMATION_DURATION);
- startSet.addAnimation(startScale);
- startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
- endBounds.height());
- startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
- // The end leash will be moved into the end position while scaling.
- final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
- endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
- final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
- endScale.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(endScale);
- // The position should be 0-based as we will post translate in
- // TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- startBounds.top - endBounds.top, 0);
- endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(endTranslate);
- // The end leash is resizing, we should update the window crop based on the clip rect.
- final Rect startClip = new Rect(startBounds);
- final Rect endClip = new Rect(endBounds);
- startClip.offsetTo(0, 0);
- endClip.offsetTo(0, 0);
- final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
- clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
- endSet.addAnimation(clipAnim);
- endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
- parentBounds.height());
- endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
-
- return new Animation[]{startSet, endSet};
- }
-
- @NonNull
- Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
- @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation;
- // Background color on TaskDisplayArea has already been set earlier in
- // WindowContainer#getAnimationAdapter.
- if (target.showBackdrop) {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
- : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
- } else {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_open_enter
- : com.android.internal.R.anim.task_fragment_open_exit);
- }
- // Use the whole animation bounds instead of the change bounds, so that when multiple change
- // targets are opening at the same time, the animation applied to each will be the same.
- // Otherwise, we may see gap between the activities that are launching together.
- animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
- wholeAnimationBounds.width(), wholeAnimationBounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- @NonNull
- Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
- @NonNull Rect wholeAnimationBounds) {
- final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation;
- if (target.showBackdrop) {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
- : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
- } else {
- animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_close_enter
- : com.android.internal.R.anim.task_fragment_close_exit);
- }
- // Use the whole animation bounds instead of the change bounds, so that when multiple change
- // targets are closing at the same time, the animation applied to each will be the same.
- // Otherwise, we may see gap between the activities that are finishing together.
- animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
- wholeAnimationBounds.width(), wholeAnimationBounds.height());
- animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- return animation;
- }
-
- private float getTransitionAnimationScaleSetting() {
- return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault)));
- }
-
- private class SettingsObserver extends ContentObserver {
- SettingsObserver(@NonNull Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- }
- }
-}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 4267749dfa6b..c5aaddc4e0ed 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -29,6 +29,7 @@ import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.extensions.embedding.AnimationBackground;
+import androidx.window.extensions.embedding.AnimationParams;
import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
@@ -112,5 +113,13 @@ public class WindowExtensionsTest {
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
assertThat(splitAttributes.getAnimationBackground())
.isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+ assertThat(splitAttributes.getAnimationParams().getAnimationBackground())
+ .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
+ assertThat(splitAttributes.getAnimationParams().getOpenAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+ assertThat(splitAttributes.getAnimationParams().getCloseAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
+ assertThat(splitAttributes.getAnimationParams().getChangeAnimationResId())
+ .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 7b473b04548c..ad41b18dcbc6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -23,8 +23,6 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTest
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -86,24 +84,6 @@ public class JetpackTaskFragmentOrganizerTest {
}
@Test
- public void testUnregisterOrganizer() {
- mOrganizer.overrideSplitAnimation();
- mOrganizer.unregisterOrganizer();
-
- verify(mOrganizer).unregisterRemoteAnimations();
- }
-
- @Test
- public void testOverrideSplitAnimation() {
- assertNull(mOrganizer.mAnimationController);
-
- mOrganizer.overrideSplitAnimation();
-
- assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index efeec82b782e..99c0ee29962c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -103,8 +103,6 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
-import com.android.window.flags.Flags;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -1557,8 +1555,6 @@ public class SplitControllerTest {
@Test
public void testIsActivityEmbedded() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
assertFalse(mSplitController.isActivityEmbedded(mActivity));
doReturn(true).when(mActivityWindowInfo).isEmbedded();
@@ -1568,8 +1564,6 @@ public class SplitControllerTest {
@Test
public void testGetEmbeddedActivityWindowInfo() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final boolean isEmbedded = true;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
@@ -1584,8 +1578,6 @@ public class SplitControllerTest {
@Test
public void testSetEmbeddedActivityWindowInfoCallback() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final ClientTransactionListenerController controller = ClientTransactionListenerController
.getInstance();
spyOn(controller);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
deleted file mode 100644
index a1e9f08585f6..000000000000
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.Mockito.never;
-
-import android.platform.test.annotations.Presubmit;
-import android.window.TaskFragmentOrganizer;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Test class for {@link TaskFragmentAnimationController}.
- *
- * Build/Install/Run:
- * atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaskFragmentAnimationControllerTest {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- @Mock
- private TaskFragmentOrganizer mOrganizer;
- private TaskFragmentAnimationController mAnimationController;
-
- @Before
- public void setup() {
- mAnimationController = new TaskFragmentAnimationController(mOrganizer);
- }
-
- @Test
- public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations();
-
- verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
-
- mAnimationController.registerRemoteAnimations();
-
- // No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- }
-
- @Test
- public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations();
-
- // No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations();
-
- mAnimationController.registerRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations();
-
- mAnimationController.unregisterRemoteAnimations();
-
- // No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations();
- }
-}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index d751b791e3e5..a00d003001e6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -190,6 +190,15 @@ java_library {
],
}
+java_library {
+ name: "WindowManager-Shell-shared-desktopMode",
+
+ srcs: [
+ "shared/**/desktopmode/*.java",
+ "shared/**/desktopmode/*.kt",
+ ],
+}
+
android_library {
name: "WindowManager-Shell",
srcs: [
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index ebebd8a52c9a..cb422eab372b 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,5 +1,5 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 3ff40e0886a4..56ea7c201e53 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -113,16 +113,6 @@ flag {
}
flag {
- name: "animate_bubble_size_change"
- namespace: "multitasking"
- description: "Turns on the animation for bubble bar icons size change"
- bug: "335575529"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_taskbar_on_phones"
namespace: "multitasking"
description: "Enables taskbar on phones"
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index c6dbd9b25e7f..1871203c7600 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -22,6 +22,40 @@ package {
default_team: "trendy_team_multitasking_windowing",
}
+android_app {
+ name: "WMShellRobolectricScreenshotTestApp",
+ platform_apis: true,
+ certificate: "platform",
+ static_libs: [
+ "WindowManager-Shell",
+ "platform-screenshot-diff-core",
+ ],
+ 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/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
index a7a3f1313a9b..b4bdaeaf0eac 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
@@ -16,7 +16,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell.multivalentscreenshot">
<application android:debuggable="true" android:supportsRtl="true">
- <uses-library android:name="android.test.runner" />
<activity
android:name="platform.test.screenshot.ScreenshotActivity"
android:exported="true">
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
new file mode 100644
index 000000000000..e02c89ae07bd
--- /dev/null
+++ 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
new file mode 100644
index 000000000000..e02c89ae07bd
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
index 7a0527ccaafb..d50d976c9e84 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
@@ -1,2 +1,3 @@
sdk=NEWEST_SDK
+graphicsMode=NATIVE
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
new file mode 100644
index 000000000000..7d912a24c443
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index d5724cc6a420..864f7cd421ee 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -19,6 +19,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/desktop_mode_handle_menu_width"
android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:paddingRight="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="vertical">
<LinearLayout
@@ -27,7 +31,7 @@
android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
android:layout_marginStart="1dp"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="horizontal"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -73,7 +77,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="horizontal"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -124,7 +128,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
@@ -135,5 +139,24 @@
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/open_in_browser_pill"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
+ android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+ android:layout_marginStart="1dp"
+ android:orientation="vertical"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:background="@drawable/desktop_mode_decor_handle_menu_background">
+
+ <Button
+ android:id="@+id/open_in_browser_button"
+ android:contentDescription="@string/open_in_browser_text"
+ android:text="@string/open_in_browser_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton"/>
+ </LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 4b9be47f8023..a4711d4180bc 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -96,7 +96,7 @@
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن تک‌ضرب بزنید."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دو تک‌ضرب بزنید"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراه‌اندازی شود؟"</string>
@@ -104,7 +104,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراه‌اندازی"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
- <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدوضربه بزنید"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدو تک‌ضرب بزنید"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
<string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 74d822ac02c0..7feb772eb10a 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -31,11 +31,11 @@
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string>
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ajouter à la réserve"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
- <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge l\'écran partagé"</string>
- <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette application ne peut être ouverte que dans une seule fenêtre."</string>
- <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
- <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
+ <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner avec l\'écran partagé"</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'appli ne prend pas en charge l\'écran partagé"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette appli ne peut être ouverte que dans une seule fenêtre."</string>
+ <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'appli ne fonctionne pas sur un écran secondaire."</string>
+ <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'appli ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
<string name="divider_title" msgid="1963391955593749442">"Séparateur d\'écran partagé"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string>
@@ -56,7 +56,7 @@
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'appli"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode Une main"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'appli <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu déroulant"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Replacer sur la pile"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
@@ -73,7 +73,7 @@
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette application"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette appli"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Aucune bulle récente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Les bulles récentes et les bulles ignorées s\'afficheront ici"</string>
@@ -88,29 +88,29 @@
<skip />
<!-- no translation found for bubble_shortcut_long_label (6088437544312894043) -->
<skip />
- <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage"</string>
- <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette application dans les paramètres"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette appli afin d\'obtenir un meilleur affichage"</string>
+ <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette appli dans les paramètres"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string>
<string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
<string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
<string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
- <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre application pour utiliser l\'écran partagé"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
+ <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour un meilleur affichage?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
- <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette application"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette appli"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
- <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
+ <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'appli"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
<string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
<string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 1161eb608405..27b79019a57a 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -117,7 +117,7 @@
<string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
<string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
<string name="select_text" msgid="5139083974039906583">"Velg"</string>
- <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string>
+ <string name="screenshot_text" msgid="1477704010087786671">"Skjermbilde"</string>
<string name="close_text" msgid="4986518933445178928">"Lukk"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
<string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 595d34664cfa..269a58693a24 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -495,9 +495,16 @@
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
- <!-- The width of the handle menu in desktop mode. -->
+ <!-- The width of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
+ spacing between them plus 4dp top padding. -->
+ <dimen name="desktop_mode_handle_menu_height">218dp</dimen>
+
+ <!-- The elevation set on the handle menu pills. -->
+ <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+
<!-- The height of the handle menu's "App Info" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
@@ -507,8 +514,11 @@
<!-- The height of the handle menu's "More Actions" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen>
- <!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">328dp</dimen>
+ <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+
+ <!-- The margin between pills of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -516,9 +526,6 @@
<!-- The start margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
- <!-- The margin between pills of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
-
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 47846746b205..4e7cfb638a12 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -280,6 +280,8 @@
<string name="select_text">Select</string>
<!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] -->
<string name="screenshot_text">Screenshot</string>
+ <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
+ <string name="open_in_browser_text">Open in browser</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
new file mode 100644
index 000000000000..f0d80a02243a
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.desktopmode
+
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.window.flags.Flags
+
+/*
+ * A shared class to check desktop mode flags state.
+ *
+ * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
+ * value and the developer option override state (if applicable).
+ **/
+enum class DesktopModeFlags(
+ // Function called to obtain aconfig flag value.
+ private val flagFunction: () -> Boolean,
+ // Whether the flag state should be affected by developer option.
+ private val shouldOverrideByDevOption: Boolean
+) {
+ // All desktop mode related flags will be added here
+ DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true);
+
+ /**
+ * Determines state of flag based on the actual flag and desktop mode developer option overrides.
+ *
+ * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
+ */
+ fun isEnabled(context: Context): Boolean =
+ if (!Flags.showDesktopWindowingDevOption() ||
+ !shouldOverrideByDevOption ||
+ context.contentResolver == null) {
+ flagFunction()
+ } else {
+ val shouldToggleBeEnabledByDefault = DesktopModeStatus.shouldDevOptionBeEnabledByDefault()
+ when (getToggleOverride(context)) {
+ ToggleOverride.OVERRIDE_UNSET -> flagFunction()
+ // When toggle override matches its default state, don't override flags. This helps users
+ // reset their feature overrides.
+ ToggleOverride.OVERRIDE_OFF ->
+ if (shouldToggleBeEnabledByDefault) false else flagFunction()
+ ToggleOverride.OVERRIDE_ON -> if (shouldToggleBeEnabledByDefault) flagFunction() else true
+ }
+ }
+
+ private fun getToggleOverride(context: Context): ToggleOverride {
+ val override =
+ cachedToggleOverride
+ ?: run {
+ val override = getToggleOverrideFromSystem(context)
+ // Cache toggle override the first time we encounter context. Override does not change
+ // with context, as context is just used to fetch System Property and Settings.Global
+ cachedToggleOverride = override
+ Log.d(TAG, "Toggle override initialized to: $override")
+ override
+ }
+
+ return override
+ }
+
+ private fun getToggleOverrideFromSystem(context: Context): ToggleOverride {
+ // A non-persistent System Property is used to store override to ensure it remains
+ // constant till reboot.
+ val overrideFromSystemProperties: ToggleOverride? =
+ System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride()
+ return overrideFromSystemProperties
+ ?: run {
+ // Read Setting Global if System Property is not present (just after reboot)
+ // or not valid (user manually changed the value)
+ val overrideFromSettingsGlobal =
+ convertToToggleOverrideWithFallback(
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
+ ToggleOverride.OVERRIDE_UNSET.setting),
+ ToggleOverride.OVERRIDE_UNSET)
+ // Initialize System Property
+ System.setProperty(
+ SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString())
+
+ overrideFromSettingsGlobal
+ }
+ }
+
+ /**
+ * Override state of desktop mode developer option toggle.
+ *
+ * @property setting The integer value that is associated with the developer option toggle
+ * override
+ */
+ enum class ToggleOverride(val setting: Int) {
+ /** No override is set. */
+ OVERRIDE_UNSET(-1),
+ /** Override to off. */
+ OVERRIDE_OFF(0),
+ /** Override to on. */
+ OVERRIDE_ON(1)
+ }
+
+ private fun String?.convertToToggleOverride(): ToggleOverride? {
+ val intValue = this?.toIntOrNull() ?: return null
+ return settingToToggleOverrideMap[intValue]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $intValue")
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeFlags"
+
+ /**
+ * Key for non-persistent System Property which is used to store desktop windowing developer
+ * option overrides.
+ */
+ private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+
+ /**
+ * Local cache for toggle override, which is initialized once on its first access. It needs to
+ * be refreshed only on reboots as overridden state takes effect on reboots.
+ */
+ private var cachedToggleOverride: ToggleOverride? = null
+
+ private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
+
+ @JvmStatic
+ fun convertToToggleOverrideWithFallback(
+ overrideInt: Int,
+ fallbackOverride: ToggleOverride
+ ): ToggleOverride {
+ return settingToToggleOverrideMap[overrideInt]
+ ?: run {
+ Log.w(TAG, "Unknown toggleOverride int $overrideInt")
+ fallbackOverride
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 92084e403846..fc4710f8f67b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -14,15 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.shared;
-
-import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
+package com.android.wm.shell.shared.desktopmode;
import android.annotation.NonNull;
import android.content.Context;
import android.os.SystemProperties;
-import android.provider.Settings;
-import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -31,6 +27,7 @@ import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
*/
+// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package
public class DesktopModeStatus {
private static final String TAG = "DesktopModeStatus";
@@ -104,16 +101,6 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
/**
- * Return {@code true} if desktop windowing flag is enabled. Only to be used for testing.
- * Callers should use {@link #canEnterDesktopMode(Context)} to query the state of desktop
- * windowing.
- */
- @VisibleForTesting
- public static boolean isDesktopModeFlagEnabled() {
- return Flags.enableDesktopWindowingMode();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -169,7 +156,7 @@ public class DesktopModeStatus {
/** Returns if desktop mode dev option should be enabled if there is no user override. */
public static boolean shouldDevOptionBeEnabledByDefault() {
- return isDesktopModeFlagEnabled();
+ return Flags.enableDesktopWindowingMode();
}
/**
@@ -178,19 +165,7 @@ public class DesktopModeStatus {
public static boolean canEnterDesktopMode(@NonNull Context context) {
if (!isDeviceEligibleForDesktopMode(context)) return false;
- // If dev option has ever been manually toggled by the user, return its value
- // TODO(b/348193756) : Move the logic for DW override based on toggle overides to a common
- // infrastructure and add caching for the computation
- int defaultOverrideState = -1;
- int toggleState = Settings.Global.getInt(context.getContentResolver(),
- DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, defaultOverrideState);
- if (toggleState != defaultOverrideState) {
- Log.d(TAG, "Using Desktop mode dev option overridden state");
- return toggleState != 0;
- }
-
- // Return Desktop windowing flag value
- return isDesktopModeFlagEnabled();
+ return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
}
/**
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 000000000000..2fabd4a33586
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ebdea1bba942..f014e559d4b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -24,6 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.CAMERA_CONTROL_STATE_UPDATE;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_APPEARED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_CLICKED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -31,7 +34,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.LocusId;
@@ -57,6 +59,11 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -75,8 +82,7 @@ import java.util.function.Consumer;
* Unified task organizer for all components in the shell.
* TODO(b/167582004): may consider consolidating this class and TaskOrganizer
*/
-public class ShellTaskOrganizer extends TaskOrganizer implements
- CompatUIController.CompatUICallback {
+public class ShellTaskOrganizer extends TaskOrganizer {
private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
@@ -194,12 +200,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
* In charge of showing compat UI. Can be {@code null} if the device doesn't support size
* compat or if this isn't the main {@link ShellTaskOrganizer}.
*
- * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController},
- * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be
- * initialized with a {@code null} {@link CompatUIController}.
+ * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIHandler},
+ * Subclasses should be initialized with a {@code null} {@link CompatUIHandler}.
*/
@Nullable
- private final CompatUIController mCompatUI;
+ private final CompatUIHandler mCompatUI;
@NonNull
private final ShellCommandHandler mShellCommandHandler;
@@ -223,7 +228,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -235,7 +240,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
protected ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -252,7 +257,21 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
- mCompatUI.setCompatUICallback(this);
+ mCompatUI.setCallback(compatUIEvent -> {
+ switch(compatUIEvent.getEventId()) {
+ case SIZE_COMPAT_RESTART_BUTTON_APPEARED:
+ onSizeCompatRestartButtonAppeared(compatUIEvent.asType());
+ break;
+ case SIZE_COMPAT_RESTART_BUTTON_CLICKED:
+ onSizeCompatRestartButtonClicked(compatUIEvent.asType());
+ break;
+ case CAMERA_CONTROL_STATE_UPDATE:
+ onCameraControlStateUpdated(compatUIEvent.asType());
+ break;
+ default:
+
+ }
+ });
}
registerOrganizer();
}
@@ -680,6 +699,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Shows/hides the given task surface. Not for general use as changing the task visibility may
+ * conflict with other Transitions. This is currently ONLY used to temporarily hide a task
+ * while a drag is in session.
+ */
+ public void setTaskSurfaceVisibility(int taskId, boolean visible) {
+ synchronized (mLock) {
+ final TaskAppearedInfo info = mTasks.get(taskId);
+ if (info != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setVisibility(info.getLeash(), visible);
+ t.apply();
+ }
+ }
+ }
+
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
@@ -727,8 +762,26 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
- @Override
- public void onSizeCompatRestartButtonAppeared(int taskId) {
+ /** Reparents a child window surface to the task surface. */
+ public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
+ SurfaceControl.Transaction t) {
+ final TaskListener taskListener;
+ synchronized (mLock) {
+ taskListener = mTasks.contains(taskId)
+ ? getTaskListener(mTasks.get(taskId).getTaskInfo())
+ : null;
+ }
+ if (taskListener == null) {
+ ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
+ taskId);
+ return;
+ }
+ taskListener.reparentChildSurfaceToTask(taskId, sc, t);
+ }
+
+ @VisibleForTesting
+ void onSizeCompatRestartButtonAppeared(@NonNull SizeCompatRestartButtonAppeared compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -740,8 +793,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
}
- @Override
- public void onSizeCompatRestartButtonClicked(int taskId) {
+ @VisibleForTesting
+ void onSizeCompatRestartButtonClicked(@NonNull SizeCompatRestartButtonClicked compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -754,8 +808,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
}
- @Override
- public void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state) {
+ @VisibleForTesting
+ void onCameraControlStateUpdated(@NonNull CameraControlStateUpdated compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
+ final int state = compatUIEvent.getState();
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
@@ -766,22 +822,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
updateCameraCompatControlState(info.getTaskInfo().token, state);
}
- /** Reparents a child window surface to the task surface. */
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- final TaskListener taskListener;
- synchronized (mLock) {
- taskListener = mTasks.contains(taskId)
- ? getTaskListener(mTasks.get(taskId).getTaskInfo())
- : null;
- }
- if (taskListener == null) {
- ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
- taskId);
- return;
- }
- taskListener.reparentChildSurfaceToTask(taskId, sc, t);
- }
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
@@ -810,10 +850,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
// on this Task if there is any.
if (taskListener == null || !taskListener.supportCompatUI()
|| !taskInfo.appCompatTaskInfo.hasCompatUI() || !taskInfo.isVisible) {
- mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, null /* taskListener */));
return;
}
- mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, taskListener));
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d270d2b4ccf1..5696a544152c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -266,6 +266,9 @@ class ActivityEmbeddingAnimationRunner {
final Animation animation =
animationProvider.get(info, change, openingWholeScreenBounds);
if (shouldUseJumpCutForAnimation(animation)) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ return new ArrayList<>();
+ }
continue;
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
@@ -291,6 +294,9 @@ class ActivityEmbeddingAnimationRunner {
final Animation animation =
animationProvider.get(info, change, closingWholeScreenBounds);
if (shouldUseJumpCutForAnimation(animation)) {
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ return new ArrayList<>();
+ }
continue;
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index f49b90d08a75..3046307702c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -97,7 +97,7 @@ class ActivityEmbeddingAnimationSpec {
Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- final Animation customAnimation = loadCustomAnimation(info, change);
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
if (customAnimation != null) {
return customAnimation;
}
@@ -131,7 +131,7 @@ class ActivityEmbeddingAnimationSpec {
Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- final Animation customAnimation = loadCustomAnimation(info, change);
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
if (customAnimation != null) {
return customAnimation;
}
@@ -172,7 +172,7 @@ class ActivityEmbeddingAnimationSpec {
// TODO(b/293658614): Support more complicated animations that may need more than a noop
// animation as the start leash.
final Animation noopAnimation = createNoopAnimation(change);
- final Animation customAnimation = loadCustomAnimation(info, change);
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
if (customAnimation != null) {
return new Animation[]{noopAnimation, customAnimation};
}
@@ -227,7 +227,7 @@ class ActivityEmbeddingAnimationSpec {
Animation loadOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change);
+ final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -254,7 +254,7 @@ class ActivityEmbeddingAnimationSpec {
Animation loadCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
- final Animation customAnimation = loadCustomAnimation(info, change);
+ final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
final Animation animation;
if (customAnimation != null) {
animation = customAnimation;
@@ -287,14 +287,14 @@ class ActivityEmbeddingAnimationSpec {
@Nullable
private Animation loadCustomAnimation(@NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change) {
+ @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) {
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
} else {
options = info.getAnimationOptions();
}
- return loadCustomAnimationFromOptions(options, change.getMode());
+ return loadCustomAnimationFromOptions(options, mode);
}
@Nullable
@@ -319,8 +319,14 @@ class ActivityEmbeddingAnimationSpec {
return null;
}
- final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
- resId);
+ final Animation anim;
+ if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
+ // TODO(b/293658614): Consider allowing custom animations from non-default packages.
+ // Enforce limiting to animations from the default "android" package for now.
+ anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
+ } else {
+ anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId);
+ }
if (anim != null) {
return anim;
}
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 ece02711070e..8467e972526e 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
@@ -422,6 +422,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
public void onThresholdCrossed() {
mThresholdCrossed = true;
+ // There was no focus window when calling startBackNavigation, still pilfer pointers so
+ // the next focus window won't receive motion events.
+ if (mBackNavigationInfo == null) {
+ tryPilferPointers();
+ return;
+ }
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
@@ -542,6 +548,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (backNavigationInfo == null) {
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
cancelLatencyTracking();
+ tryPilferPointers();
return;
}
final int backType = backNavigationInfo.getType();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 4988a9481d21..e24df0bdc05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -30,7 +30,7 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
+import com.android.internal.jank.InteractionJankMonitor;
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
@@ -86,20 +86,21 @@ public class BackAnimationRunner {
*/
void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
+ InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
final IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
if (shouldMonitorCUJ(apps)) {
- InteractionJankMonitorUtils.endTracing(mCujType);
+ interactionJankMonitor.end(mCujType);
}
finishedCallback.run();
}
};
mWaitingAnimation = false;
if (shouldMonitorCUJ(apps)) {
- InteractionJankMonitorUtils.beginTracing(
- mCujType, mContext, apps[0].leash, /* tag */ null);
+ interactionJankMonitor.begin(
+ apps[0].leash, mContext, mCujType);
}
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
index a124f95d7431..c93c11eb2fc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
@@ -20,10 +20,10 @@ import android.app.Activity
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity to create a shortcut to open bubbles */
class CreateBubbleShortcutActivity : Activity() {
@@ -31,7 +31,7 @@ class CreateBubbleShortcutActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Flags.enableRetrievableBubbles()) {
- KtProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
+ ProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
createShortcut()
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
index ae7940ca1b65..e578e9e76979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
@@ -21,9 +21,9 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
-import com.android.wm.shell.util.KtProtoLog
/** Activity that sends a broadcast to open bubbles */
class ShowBubblesActivity : Activity() {
@@ -37,7 +37,7 @@ class ShowBubblesActivity : Activity() {
// Set the package as the receiver is not exported
`package` = packageName
}
- KtProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
+ ProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
sendBroadcast(intent)
}
finish()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index bfee820870f1..736d954513b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -54,4 +54,11 @@ public class HandlerExecutor implements ShellExecutor {
public boolean hasCallback(Runnable r) {
return mHandler.hasCallbacks(r);
}
+
+ @Override
+ public void assertCurrentThread() {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("must be called on " + mHandler);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
deleted file mode 100644
index 86f00b83cadd..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.common;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.SurfaceControl;
-import android.view.View;
-
-import com.android.internal.jank.Cuj.CujType;
-import com.android.internal.jank.InteractionJankMonitor;
-
-/** Utils class for simplfy InteractionJank trancing call */
-public class InteractionJankMonitorUtils {
-
- /**
- * Begin a trace session.
- *
- * @param cujType the specific {@link CujType}.
- * @param view the view to trace
- * @param tag the tag to distinguish different flow of same type CUJ.
- */
- public static void beginTracing(@CujType int cujType,
- @NonNull View view, @Nullable String tag) {
- final InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
- if (!TextUtils.isEmpty(tag)) {
- builder.setTag(tag);
- }
- InteractionJankMonitor.getInstance().begin(builder);
- }
-
- /**
- * Begin a trace session.
- *
- * @param cujType the specific {@link CujType}.
- * @param context the context
- * @param surface the surface to trace
- * @param tag the tag to distinguish different flow of same type CUJ.
- */
- public static void beginTracing(@CujType int cujType,
- @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
- final InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
- if (!TextUtils.isEmpty(tag)) {
- builder.setTag(tag);
- }
- InteractionJankMonitor.getInstance().begin(builder);
- }
-
- /**
- * End a trace session.
- *
- * @param cujType the specific {@link CujType}.
- */
- public static void endTracing(@CujType int cujType) {
- InteractionJankMonitor.getInstance().end(cujType);
- }
-
- /**
- * Cancel the trace session.
- *
- * @param cujType the specific {@link CujType}.
- */
- public static void cancelTracing(@CujType int cujType) {
- InteractionJankMonitor.getInstance().cancel(cujType);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
index 81592c35e4ac..e92b0b59d2ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -17,8 +17,8 @@ package com.android.wm.shell.common
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
-import com.android.wm.shell.util.KtProtoLog
/**
* Controller to manage behavior of activities launched with
@@ -30,7 +30,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
var launchAdjacentEnabled: Boolean = true
set(value) {
if (field != value) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
field = value
container?.let { c ->
if (value) {
@@ -52,7 +52,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
* @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
*/
fun setLaunchAdjacentRoot(container: WindowContainerToken) {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
this.container = container
if (launchAdjacentEnabled) {
enableContainer(container)
@@ -67,7 +67,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
* @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
*/
fun clearLaunchAdjacentRoot() {
- KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+ ProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
container?.let {
disableContainer(it)
container = null
@@ -75,14 +75,14 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
}
private fun enableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.setLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
}
private fun disableContainer(container: WindowContainerToken) {
- KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+ ProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
val wct = WindowContainerTransaction()
wct.clearLaunchAdjacentFlagRoot(container)
syncQueue.queue(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
index 9e8dfb5f0c6f..a6be64070ac1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -23,9 +23,9 @@ import android.content.pm.PackageManager
import android.os.UserHandle
import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
-import com.android.wm.shell.util.KtProtoLog
import java.util.Arrays
/**
@@ -52,7 +52,7 @@ class MultiInstanceHelper @JvmOverloads constructor(
val packageName = componentName.packageName
for (pkg in staticAppsSupportingMultiInstance) {
if (pkg == packageName) {
- KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
+ ProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
packageName)
return true
}
@@ -70,10 +70,10 @@ class MultiInstanceHelper @JvmOverloads constructor(
// If the above call doesn't throw a NameNotFoundException, then the activity property
// should override the application property value
if (activityProp.isBoolean) {
- KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
+ ProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
return activityProp.boolean
} else {
- KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
+ ProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
}
} catch (nnfe: PackageManager.NameNotFoundException) {
@@ -85,10 +85,10 @@ class MultiInstanceHelper @JvmOverloads constructor(
val appProp = packageManager.getProperty(
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
if (appProp.isBoolean) {
- KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
+ ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
return appProp.boolean
} else {
- KtProtoLog.w(WM_SHELL,
+ ProtoLog.w(WM_SHELL,
"Warning: property=%s for application=%s has non-bool type=%d",
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index f729164ed303..2c2961fd4b65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -96,4 +96,11 @@ public interface ShellExecutor extends Executor {
* See {@link android.os.Handler#hasCallbacks(Runnable)}.
*/
boolean hasCallback(Runnable runnable);
+
+ /**
+ * May throw if the caller is not on the same thread as the executor.
+ */
+ default void assertCurrentThread() {
+ return;
+ }
}
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 3ad60e7031e5..1bc179551825 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
@@ -492,6 +492,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
return mHideHandle;
}
+ /** Returns true if the divider is currently being physically controlled by the user. */
+ boolean isMoving() {
+ return mMoving;
+ }
+
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index de016d3ae400..5097ed8866c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -269,7 +269,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (update) {
if (immediately) {
+ t.setAlpha(mBackgroundLeash, showVeil ? 1f : 0f);
t.setVisibility(mBackgroundLeash, showVeil);
+ t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
t.setVisibility(mIconLeash, showVeil);
} else {
startFadeAnimation(showVeil, false, null);
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 d3c349f9c866..51f9de8305f8 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
@@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -67,7 +68,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -131,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final boolean mDimNonImeSide;
private final boolean mAllowLeftRightSplitInPortrait;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
@@ -163,6 +164,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+ mInteractionJankMonitor = InteractionJankMonitor.getInstance();
resetDividerPosition();
updateInvisibleRect();
}
@@ -569,12 +571,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
void onStartDragging() {
- InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext,
- getDividerLeash(), null /* tag */);
+ mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE);
}
void onDraggingCancelled() {
- InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE);
+ mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_RESIZE);
}
void onDoubleTappedDivider() {
@@ -638,7 +639,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
- InteractionJankMonitorUtils.endTracing(
+ mInteractionJankMonitor.end(
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
@@ -651,9 +652,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
.ofInt(from, to)
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+
+ // 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
+ // movement, we pass in true here to continue the parallax effect smoothly.
+ boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null
+ && mSplitWindowManager.getDividerView().isMoving();
+
mDividerFlingAnimator.addUpdateListener(
animation -> updateDividerBounds(
- (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+ (int) animation.getAnimatedValue(),
+ isBeingMovedByUser /* shouldUseParallaxEffect */
+ )
);
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -661,7 +671,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
- InteractionJankMonitorUtils.endTracing(
+ mInteractionJankMonitor.end(
CUJ_SPLIT_SCREEN_RESIZE);
mDividerFlingAnimator = null;
}
@@ -707,8 +717,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
- mContext, getDividerLeash(), null /*tag*/);
+ mInteractionJankMonitor.begin(getDividerLeash(),
+ mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
@@ -716,12 +726,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerPosition = dividerPos;
updateBounds(mDividerPosition);
finishCallback.accept(insets);
- InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mInteractionJankMonitor.end(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
@Override
public void onAnimationCancel(Animator animation) {
- InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
+ mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
});
set.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 5d121c23c6e1..46c1a43f9efe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -37,7 +37,6 @@ import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -192,7 +191,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mDividerView.setInteractive(interactive, hideHandle, from);
}
- View getDividerView() {
+ DividerView getDividerView() {
return mDividerView;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 6781d08c9904..d1b2347a4411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -19,6 +19,16 @@
package com.android.wm.shell.compatui
import android.app.TaskInfo
-fun isSingleTopActivityTranslucent(task: TaskInfo) =
- task.isTopActivityTransparent && task.numActivities == 1
+import android.content.Context
+import com.android.internal.R
+// TODO(b/347289970): Consider replacing with API
+fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
+ isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
+ && !task.isTopActivityStyleFloating)
+
+private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
+ val sysUiPackageName: String =
+ context.resources.getString(R.string.config_systemUi)
+ return task.baseActivity?.packageName == sysUiPackageName
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 2520c25613e7..c02c9cf3fd72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -50,6 +49,10 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,17 +74,7 @@ import java.util.function.Predicate;
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
- DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
-
- /** Callback for compat UI interaction. */
- public interface CompatUICallback {
- /** Called when the size compat restart button appears. */
- void onSizeCompatRestartButtonAppeared(int taskId);
- /** Called when the size compat restart button is clicked. */
- void onSizeCompatRestartButtonClicked(int taskId);
- /** Called when the camera compat control state is updated. */
- void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
- }
+ DisplayImeController.ImePositionProcessor, KeyguardChangeListener, CompatUIHandler {
private static final String TAG = "CompatUIController";
@@ -170,7 +163,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final Function<Integer, Integer> mDisappearTimeSupplier;
@Nullable
- private CompatUICallback mCompatUICallback;
+ private Consumer<CompatUIEvent> mCallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
@@ -230,20 +223,21 @@ public class CompatUIController implements OnDisplaysChangedListener,
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for Compat UI interactions. */
- public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
- mCompatUICallback = compatUiCallback;
+ /** Sets the callback for UI interactions. */
+ @Override
+ public void setCallback(@Nullable Consumer<CompatUIEvent> callback) {
+ mCallback = callback;
}
/**
* Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
*
- * @param taskInfo {@link TaskInfo} task the activity is in.
- * @param taskListener listener to handle the Task Surface placement.
+ * @param compatUIInfo {@link CompatUIInfo} encapsulates information about the task and listener
*/
- public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
- @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
+ final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
+ final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
@@ -466,7 +460,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCompatUICallback, taskListener,
+ taskInfo, mSyncQueue, mCallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -478,9 +472,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
taskInfoState.first)) {
// We need to show the dialog
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
- onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+ onCompatInfoChanged(new CompatUIInfo(taskInfoState.first, taskInfoState.second));
} else {
- mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(taskInfoState.first.taskId));
}
}
@@ -575,13 +569,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(stateInfo.first.taskId));
}
private void onRestartDialogDismissCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
- onCompatInfoChanged(stateInfo.first, stateInfo.second);
+ onCompatInfoChanged(new CompatUIInfo(stateInfo.first, stateInfo.second));
}
private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 3ab1fad2b203..1931212df548 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -40,8 +40,10 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
import java.util.function.Consumer;
@@ -50,10 +52,13 @@ import java.util.function.Consumer;
*/
class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
- private final CompatUICallback mCallback;
+ @NonNull
+ private final Consumer<CompatUIEvent> mCallback;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
+ @NonNull
private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
// Remember the last reported states in case visibility changes due to keyguard or IME updates.
@@ -65,6 +70,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
@VisibleForTesting
+ @NonNull
CompatUIHintsState mCompatUIHintsState;
@Nullable
@@ -73,11 +79,15 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
private final float mHideScmTolerance;
- CompatUIWindowManager(Context context, TaskInfo taskInfo,
- SyncTransactionQueue syncQueue, CompatUICallback callback,
- ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
- Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
+ CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull Consumer<CompatUIEvent> callback,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @Nullable DisplayLayout displayLayout,
+ @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>>
+ onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
@@ -122,7 +132,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
updateVisibilityOfViews();
if (mHasSizeCompat) {
- mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+ mCallback.accept(new SizeCompatRestartButtonAppeared(mTaskId));
}
return mLayout;
@@ -177,7 +187,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
: CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, mCameraCompatControlState));
mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
}
@@ -188,7 +198,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
return;
}
mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
- mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED));
mLayout.setCameraControlVisibility(/* show= */ false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
new file mode 100644
index 000000000000..4a0cf9843722
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.compatui.api
+
+/**
+ * Abstraction for all the possible Compat UI Component events.
+ */
+interface CompatUIEvent {
+ /**
+ * Unique event identifier
+ */
+ val eventId: Int
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T : CompatUIEvent> asType(): T? = this as? T
+
+ fun <T : CompatUIEvent> asType(clazz: Class<T>): T? {
+ return if (clazz.isInstance(this)) clazz.cast(this) else null
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
new file mode 100644
index 000000000000..817e554b550e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.compatui.api
+
+import java.util.function.Consumer
+
+/**
+ * Abstraction for the objects responsible to handle all the CompatUI components and the
+ * communication with the server.
+ */
+interface CompatUIHandler {
+ /**
+ * Invoked when a new model is coming from the server.
+ */
+ fun onCompatInfoChanged(compatUIInfo: CompatUIInfo)
+
+ /**
+ * Optional reference to the object responsible to send {@link CompatUIEvent}
+ */
+ fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
new file mode 100644
index 000000000000..dbbf049792f5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.compatui.api
+
+import android.app.TaskInfo
+import com.android.wm.shell.ShellTaskOrganizer
+
+/**
+ * Encapsulate the info of the message from core.
+ */
+data class CompatUIInfo(val taskInfo: TaskInfo, val listener: ShellTaskOrganizer.TaskListener?) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
new file mode 100644
index 000000000000..58ce8ed6c978
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.compatui.impl
+
+import android.app.AppCompatTaskInfo
+import android.app.CameraCompatTaskInfo
+import com.android.wm.shell.compatui.api.CompatUIEvent
+
+internal const val SIZE_COMPAT_RESTART_BUTTON_APPEARED = 0
+internal const val SIZE_COMPAT_RESTART_BUTTON_CLICKED = 1
+internal const val CAMERA_CONTROL_STATE_UPDATE = 2
+
+/**
+ * All the {@link CompatUIEvent} the Compat UI Framework can handle
+ */
+sealed class CompatUIEvents(override val eventId: Int) : CompatUIEvent {
+ /** Sent when the size compat restart button appears. */
+ data class SizeCompatRestartButtonAppeared(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED)
+
+ /** Sent when the size compat restart button is clicked. */
+ data class SizeCompatRestartButtonClicked(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED)
+
+ /** Sent when the camera compat control state is updated. */
+ data class CameraControlStateUpdated(
+ val taskId: Int,
+ @CameraCompatTaskInfo.CameraCompatControlState val state: Int
+ ) : CompatUIEvents(CAMERA_CONTROL_STATE_UPDATE)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
new file mode 100644
index 000000000000..a181eafada7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -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.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIEvent
+import com.android.wm.shell.compatui.api.CompatUIHandler
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import java.util.function.Consumer
+
+/**
+ * Default implementation of {@link CompatUIHandler} to handle CompatUI components
+ */
+class DefaultCompatUIHandler : CompatUIHandler {
+
+ private var compatUIEventSender: Consumer<CompatUIEvent>? = null
+ override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
+ // Empty at the moment
+ }
+
+ override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
+ this.compatUIEventSender = compatUIEventSender
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 609e5af5c5b0..9bdc0b2b55b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -71,6 +71,8 @@ import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -88,12 +90,12 @@ import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.TaskStackTransitionObserver;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -211,7 +213,7 @@ public abstract class WMShellBaseModule {
Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- Optional<CompatUIController> compatUI,
+ Optional<CompatUIHandler> compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -230,7 +232,7 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<CompatUIController> provideCompatUIController(
+ static Optional<CompatUIHandler> provideCompatUIController(
Context context,
ShellInit shellInit,
ShellController shellController,
@@ -247,6 +249,9 @@ public abstract class WMShellBaseModule {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
+ if (Flags.appCompatUiFramework()) {
+ return Optional.of(new DefaultCompatUIHandler());
+ }
return Optional.of(
new CompatUIController(
context,
@@ -898,7 +903,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopTasksController.flatMap((lazy)-> {
+ return desktopTasksController.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
@@ -917,7 +922,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy)-> {
+ return desktopModeTaskRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
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 4ea41d5256f9..45feff5b0ccc 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
@@ -24,7 +24,6 @@ import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
import android.view.IWindowManager;
-import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.internal.jank.InteractionJankMonitor;
@@ -78,10 +77,10 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -222,7 +221,8 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ InteractionJankMonitor interactionJankMonitor) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -239,12 +239,16 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ interactionJankMonitor);
}
return new CaptionWindowDecorViewModel(
context,
mainHandler,
+ mainExecutor,
mainChoreographer,
+ windowManager,
+ shellInit,
taskOrganizer,
displayController,
rootTaskDisplayAreaOrganizer,
@@ -402,8 +406,7 @@ public abstract class WMShellModule {
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
return new RecentsTransitionHandler(shellInit, transitions,
- recentTasksController.orElse(null), homeTransitionObserver,
- SurfaceControl.Transaction::new);
+ recentTasksController.orElse(null), homeTransitionObserver);
}
//
@@ -577,17 +580,18 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
- Transitions transitions) {
- return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
+ return new ToggleResizeDesktopTaskTransitionHandler(transitions, interactionJankMonitor);
}
@WMSingleton
@Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context
- ) {
- return new ExitDesktopTaskTransitionHandler(transitions, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ExitDesktopTaskTransitionHandler(
+ transitions, context, interactionJankMonitor);
}
@WMSingleton
@@ -603,11 +607,12 @@ public abstract class WMShellModule {
Context context,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
Transitions transitions,
+ ShellTaskOrganizer shellTaskOrganizer,
ShellInit shellInit
) {
return desktopModeTaskRepository.flatMap(repository ->
Optional.of(new DesktopTasksTransitionObserver(
- context, repository, transitions, shellInit))
+ context, repository, transitions, shellTaskOrganizer, shellInit))
);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 240cf3b96e89..037fbb235bd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -38,12 +38,10 @@ import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -79,7 +77,7 @@ import java.util.Optional;
public abstract class Pip1Module {
@WMSingleton
@Provides
- static Optional<Pip> providePip1(Context context,
+ static Optional<PipController.PipImpl> providePip1(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -104,20 +102,16 @@ public abstract class Pip1Module {
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- if (PipUtils.isPip2ExperimentEnabled()) {
- return Optional.empty();
- } else {
- return Optional.ofNullable(PipController.create(
- context, shellInit, shellCommandHandler, shellController,
- displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController,
- mainExecutor));
- }
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController,
+ mainExecutor));
}
// Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 696831747865..ea7e9685dd92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -81,6 +81,16 @@ public abstract class Pip2Module {
@WMSingleton
@Provides
+ static Optional<PipController.PipImpl> providePip2(Optional<PipController> pipController) {
+ if (pipController.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(pipController.get().getPipImpl());
+ }
+ }
+
+ @WMSingleton
+ @Provides
static Optional<PipController> providePipController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index f2631eff890d..a3afe7860f2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -18,12 +18,16 @@ package com.android.wm.shell.dagger.pip;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies for external components / modules reference PiP and extracts away the
* selection of legacy and new PiP implementation.
@@ -44,4 +48,17 @@ public abstract class PipModule {
return legacyPipTransition;
}
}
+
+ @WMSingleton
+ @Provides
+ static Optional<Pip> providePip(
+ Optional<com.android.wm.shell.pip.phone.PipController.PipImpl> pip1,
+ Optional<PipController.PipImpl> pip2) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.ofNullable(pip2.orElse(null));
+
+ } else {
+ return Optional.ofNullable(pip1.orElse(null));
+ }
+ }
}
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 fbc11c19a5a2..400882a8da9a 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
@@ -16,9 +16,9 @@
package com.android.wm.shell.desktopmode
+import com.android.internal.protolog.ProtoLog
import com.android.internal.util.FrameworkStatsLog
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.util.KtProtoLog
/** Event logger for logging desktop mode session events */
class DesktopModeEventLogger {
@@ -27,7 +27,7 @@ class DesktopModeEventLogger {
* entering desktop mode
*/
fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session enter, session: %s reason: %s",
sessionId,
@@ -47,7 +47,7 @@ class DesktopModeEventLogger {
* exiting desktop mode
*/
fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session exit, session: %s reason: %s",
sessionId,
@@ -67,7 +67,7 @@ class DesktopModeEventLogger {
* session id [sessionId]
*/
fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task added, session: %s taskId: %s",
sessionId,
@@ -99,7 +99,7 @@ class DesktopModeEventLogger {
* session id [sessionId]
*/
fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task remove, session: %s taskId: %s",
sessionId,
@@ -131,7 +131,7 @@ class DesktopModeEventLogger {
* having session id [sessionId]
*/
fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
sessionId,
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 a67dee3a4a8d..066b5ad39d0f 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
@@ -46,11 +46,10 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
/**
* A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -106,7 +105,7 @@ class DesktopModeLoggerTransitionObserver(
) {
// this was a new recents animation
if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Recents animation running, saving tasks for later"
)
@@ -132,7 +131,7 @@ class DesktopModeLoggerTransitionObserver(
info.flags == 0 &&
tasksSavedForRecents.isNotEmpty()
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Canceled recents animation, restoring tasks"
)
@@ -202,7 +201,7 @@ class DesktopModeLoggerTransitionObserver(
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: taskInfo map after processing changes %s",
postTransitionFreeformTasks.size()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 81891ce91e04..ca0586418041 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -26,8 +26,8 @@ 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.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -142,7 +142,7 @@ class DesktopModeTaskRepository {
val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
if (added) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add active task=%d displayId=%d",
taskId,
@@ -167,7 +167,7 @@ class DesktopModeTaskRepository {
}
}
if (result) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
}
return result
}
@@ -180,7 +180,7 @@ class DesktopModeTaskRepository {
fun addClosingTask(displayId: Int, taskId: Int): Boolean {
val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
if (added) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: added closing task=%d displayId=%d",
taskId,
@@ -203,7 +203,7 @@ class DesktopModeTaskRepository {
}
}
if (removed) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
}
return removed
}
@@ -250,6 +250,10 @@ class DesktopModeTaskRepository {
return ArraySet(displayData[displayId]?.activeTasks)
}
+ /** Returns the minimized tasks for the given [displayId]. */
+ fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
+ ArraySet(displayData[displayId]?.minimizedTasks)
+
/**
* Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
* are visible.
@@ -312,14 +316,14 @@ class DesktopModeTaskRepository {
// Check if count changed
if (prevCount != newCount) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
taskId,
visible,
displayId
)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
prevCount,
@@ -337,7 +341,7 @@ class DesktopModeTaskRepository {
/** Get number of tasks that are marked as visible on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
displayData[displayId]?.visibleTasks?.size ?: 0
@@ -349,7 +353,7 @@ class DesktopModeTaskRepository {
// TODO(b/342417921): Identify if there is additional checks needed to move tasks for
// multi-display scenarios.
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
displayId,
@@ -361,7 +365,7 @@ class DesktopModeTaskRepository {
/** Mark a Task as minimized. */
fun minimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
displayId,
@@ -372,7 +376,7 @@ class DesktopModeTaskRepository {
/** Mark a Task as non-minimized. */
fun unminimizeTask(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
displayId,
@@ -383,7 +387,7 @@ class DesktopModeTaskRepository {
/** Remove the task from the ordered list. */
fun removeFreeformTask(displayId: Int, taskId: Int) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
displayId,
@@ -391,7 +395,7 @@ class DesktopModeTaskRepository {
)
displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remaining freeform tasks: %s",
displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 217b1d356122..1bf125938e6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -19,6 +19,8 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.TaskInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
import android.content.pm.ActivityInfo.isFixedOrientationLandscape
import android.content.pm.ActivityInfo.isFixedOrientationPortrait
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -105,7 +107,7 @@ fun calculateInitialBounds(
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
-private fun maximumSizeMaintainingAspectRatio(
+fun maximumSizeMaintainingAspectRatio(
taskInfo: RunningTaskInfo,
targetArea: Size,
aspectRatio: Float
@@ -114,7 +116,8 @@ private fun maximumSizeMaintainingAspectRatio(
val targetWidth = targetArea.width
val finalHeight: Int
val finalWidth: Int
- if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+ // Get orientation either through top activity or task's orientation
+ if (taskInfo.hasPortraitTopActivity()) {
val tempWidth = (targetHeight / aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
@@ -137,7 +140,7 @@ private fun maximumSizeMaintainingAspectRatio(
}
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
-private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
@@ -171,3 +174,41 @@ private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
desiredSize.height + heightOffset
)
}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
+ * entire screen, as area can be offset by left and top start.
+ */
+fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
+ val heightOffset = (areaBounds.height() - desiredSize.height) / 2
+ val widthOffset = (areaBounds.width() - desiredSize.width) / 2
+
+ val newLeft = leftStart + widthOffset
+ val newTop = topStart + heightOffset
+ val newRight = newLeft + desiredSize.width
+ val newBottom = newTop + desiredSize.height
+
+ return Rect(newLeft, newTop, newRight, newBottom)
+}
+
+fun TaskInfo.hasPortraitTopActivity(): Boolean {
+ val topActivityScreenOrientation =
+ topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
+ val appBounds = configuration.windowConfiguration.appBounds
+
+ return when {
+ // First check if activity has portrait screen orientation
+ topActivityScreenOrientation != SCREEN_ORIENTATION_UNSPECIFIED -> {
+ isFixedOrientationPortrait(topActivityScreenOrientation)
+ }
+
+ // Then check if the activity is portrait when letterboxed
+ appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+
+ // Then check if the activity is portrait
+ appBounds != null -> appBounds.height() > appBounds.width()
+
+ // Otherwise just take the orientation of the task
+ else -> isFixedOrientationPortrait(configuration.orientation)
+ }
+}
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 5813f8513b06..580724666949 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
@@ -36,6 +36,7 @@ import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
+import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
@@ -49,6 +50,7 @@ import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -65,7 +67,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
+import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -73,9 +75,9 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
-import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
-import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -87,7 +89,6 @@ import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -157,20 +158,6 @@ class DesktopTasksController(
visualIndicator = null
}
}
- private val sysUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
-
- private val transitionAreaHeight
- get() =
- context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
- )
-
- private val transitionAreaWidth
- get() =
- context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
- )
/** Task id of the task currently being dragged from fullscreen/split. */
val draggingTaskId
@@ -187,7 +174,7 @@ class DesktopTasksController(
}
private fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
@@ -201,7 +188,7 @@ class DesktopTasksController(
recentsTransitionHandler.addTransitionStateListener(
object : RecentsTransitionStateListener {
override fun onAnimationStateChanged(running: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: recents animation state changed running=%b",
running
@@ -218,11 +205,6 @@ class DesktopTasksController(
return visualIndicator
}
- // TODO(b/347289970): Consider replacing with API
- private fun isSystemUIApplication(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.baseActivity?.packageName == sysUIPackageName
- }
-
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -235,15 +217,23 @@ class DesktopTasksController(
dragToDesktopTransitionHandler.setSplitScreenController(controller)
}
+ /** Returns the transition type for the given remote transition. */
+ private fun transitionType(remoteTransition: RemoteTransition?): Int {
+ if (remoteTransition == null) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: remoteTransition is null")
+ return TRANSIT_NONE
+ }
+ return TRANSIT_TO_FRONT
+ }
+
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
val wct = WindowContainerTransaction()
bringDesktopAppsToFront(displayId, wct)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/309014605): ensure remote transition is supplied once state is introduced
- val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+ val transitionType = transitionType(remoteTransition)
val handler =
remoteTransition?.let {
OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -288,7 +278,7 @@ class DesktopTasksController(
moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource)
}
else -> {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, expected less " +
"than 3 focused tasks but found %d",
@@ -318,7 +308,7 @@ class DesktopTasksController(
transitionSource: DesktopModeTransitionSource,
): Boolean {
recentTasksController?.findTaskInBackground(taskId)?.let {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d",
taskId
@@ -350,23 +340,16 @@ class DesktopTasksController(
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
) {
- if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
- KtProtoLog.w(
+ if (Flags.enableDesktopWindowingModalsPolicy()
+ && isTopActivityExemptFromDesktopWindowing(context, task)) {
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: Cannot enter desktop, " +
- "translucent top activity found. This is likely a modal dialog."
+ "ineligible top activity found."
)
return
}
- if (isSystemUIApplication(task)) {
- KtProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: Cannot enter desktop, " +
- "systemUI top activity found."
- )
- return
- }
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
@@ -393,7 +376,7 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: startDragToDesktop taskId=%d",
taskInfo.taskId
@@ -409,14 +392,14 @@ class DesktopTasksController(
* [startDragToDesktop].
*/
private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTaskToFront(wct)
+ moveHomeTask(wct, toTop = true)
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
@@ -453,7 +436,7 @@ class DesktopTasksController(
}
if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: the task with taskId=%d is already closing!",
taskId
@@ -477,7 +460,7 @@ class DesktopTasksController(
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToSplit taskId=%d",
task.taskId
@@ -510,7 +493,7 @@ class DesktopTasksController(
* [startDragToDesktop].
*/
fun cancelDragToDesktop(task: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: cancelDragToDesktop taskId=%d",
task.taskId
@@ -525,7 +508,7 @@ class DesktopTasksController(
position: Point,
transitionSource: DesktopModeTransitionSource
) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToFullscreen with animation taskId=%d",
task.taskId
@@ -553,7 +536,7 @@ class DesktopTasksController(
/** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveTaskToFront taskId=%d",
taskInfo.taskId
@@ -584,10 +567,10 @@ class DesktopTasksController(
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (task == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
return
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToNextDisplay: taskId=%d taskDisplayId=%d",
taskId,
@@ -602,7 +585,7 @@ class DesktopTasksController(
newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
}
if (newDisplayId == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
return
}
moveToDisplay(task, newDisplayId)
@@ -614,7 +597,7 @@ class DesktopTasksController(
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"moveToDisplay: taskId=%d displayId=%d",
task.taskId,
@@ -622,13 +605,13 @@ class DesktopTasksController(
)
if (task.displayId == displayId) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
return
}
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
return
}
@@ -649,13 +632,21 @@ class DesktopTasksController(
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
val destinationBounds = Rect()
- if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
- // The desktop task is currently occupying the whole stable bounds. If the bounds
- // before the task was toggled to stable bounds were saved, toggle the task to those
- // bounds. Otherwise, toggle to the default bounds.
+
+ val isMaximized = if (taskInfo.isResizeable) {
+ currentTaskBounds == stableBounds
+ } else {
+ currentTaskBounds.width() == stableBounds.width()
+ || currentTaskBounds.height() == stableBounds.height()
+ }
+
+ if (isMaximized) {
+ // The desktop task is at the maximized width and/or height of the stable bounds.
+ // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
+ // Otherwise, toggle to the default bounds.
val taskBoundsBeforeMaximize =
desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
if (taskBoundsBeforeMaximize != null) {
@@ -670,9 +661,20 @@ class DesktopTasksController(
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
- val taskBounds = taskInfo.configuration.windowConfiguration.bounds
- desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds)
- destinationBounds.set(stableBounds)
+ desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
+
+ if (taskInfo.isResizeable) {
+ // if resizable then expand to entire stable bounds (full display minus insets)
+ destinationBounds.set(stableBounds)
+ } else {
+ // if non-resizable then calculate max bounds according to aspect ratio
+ val activityAspectRatio = calculateAspectRatio(taskInfo)
+ val newSize = maximumSizeMaintainingAspectRatio(taskInfo,
+ Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+ val newBounds = centerInArea(
+ newSize, stableBounds, stableBounds.left, stableBounds.top)
+ destinationBounds.set(newBounds)
+ }
}
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -764,18 +766,19 @@ class DesktopTasksController(
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
newTaskIdInFront ?: "null"
)
- if (Flags.enableDesktopWindowingWallpaperActivity()) {
+ // Move home to front, ensures that we go back home when all desktop windows are closed
+ moveHomeTask(wct, toTop = true)
+
+ // Currently, we only handle the desktop on the default display really.
+ if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
// Add translucent wallpaper activity to show the wallpaper underneath
addWallpaperActivity(wct)
- } else {
- // Move home to front
- moveHomeTaskToFront(wct)
}
val nonMinimizedTasksOrderedFrontToBack =
@@ -801,15 +804,15 @@ class DesktopTasksController(
return taskToMinimize
}
- private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+ private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
shellTaskOrganizer
.getRunningTasks(context.displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
@@ -829,7 +832,7 @@ class DesktopTasksController(
private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
desktopModeTaskRepository.wallpaperActivityToken?.let { token ->
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
wct.removeTask(token)
}
}
@@ -867,7 +870,7 @@ class DesktopTasksController(
transition: IBinder,
request: TransitionRequestInfo
): WindowContainerTransaction? {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest request=%s",
request
@@ -909,7 +912,7 @@ class DesktopTasksController(
}
if (!shouldHandleRequest) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: skipping handleRequest reason=%s",
reason
@@ -922,10 +925,8 @@ class DesktopTasksController(
when {
// Check if the closing task needs to be handled
TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
- // Check if the task has a top transparent activity
- shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task)
- // Check if the task has a top systemUI activity
- isSystemUIApplication(task) -> handleIncompatibleTaskLaunch(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
task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
// Check if freeform task should be updated
@@ -935,7 +936,7 @@ class DesktopTasksController(
}
}
}
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: handleRequest result=%s",
result ?: "null"
@@ -959,9 +960,9 @@ class DesktopTasksController(
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
- // TODO(b/347289970): Consider replacing with API
- private fun shouldLaunchAsModal(task: TaskInfo) =
- Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
+ private fun isIncompatibleTask(task: TaskInfo) =
+ Flags.enableDesktopWindowingModalsPolicy()
+ && isTopActivityExemptFromDesktopWindowing(context, task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
return Flags.enableDesktopWindowingWallpaperActivity() &&
@@ -973,15 +974,15 @@ class DesktopTasksController(
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
if (keyguardManager.isKeyguardLocked) {
// Do NOT handle freeform task launch when locked.
// It will be launched in fullscreen windowing mode (Details: b/160925539)
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
return null
}
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bring desktop tasks to front on transition" +
" taskId=%d",
@@ -1010,9 +1011,9 @@ class DesktopTasksController(
task: RunningTaskInfo,
transition: IBinder
): WindowContainerTransaction? {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
- KtProtoLog.d(
+ ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
" taskId=%d",
@@ -1020,6 +1021,9 @@ class DesktopTasksController(
)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
+ // In some launches home task is moved behind new task being launched. Make sure
+ // that's not the case for launches in desktop.
+ moveHomeTask(wct, toTop = false)
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
@@ -1052,7 +1056,7 @@ class DesktopTasksController(
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: the task with taskId=%d is already closing!",
task.taskId
@@ -1103,6 +1107,10 @@ class DesktopTasksController(
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/**
@@ -1118,6 +1126,10 @@ class DesktopTasksController(
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
@@ -1391,7 +1403,7 @@ class DesktopTasksController(
if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
// TODO(b/320797628): Should only return early if there is an existing running task, and
// notify the user as well. But for now, just ignore the drop.
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
return false
}
@@ -1482,7 +1494,7 @@ class DesktopTasksController(
private val listener: VisibleTasksListener =
object : VisibleTasksListener {
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
@@ -1527,11 +1539,11 @@ class DesktopTasksController(
}
override fun stashDesktopApps(displayId: Int) {
- KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated")
}
override fun hideStashedDesktopApps(displayId: Int) {
- KtProtoLog.w(
+ ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: hideStashedDesktopApps is deprecated"
)
@@ -1558,7 +1570,7 @@ class DesktopTasksController(
}
override fun setTaskListener(listener: IDesktopTaskListener?) {
- KtProtoLog.v(
+ ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: set task listener=%s",
listener ?: "null"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 0f88384ec2ac..534cc22ada47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -23,12 +23,12 @@ import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
-import com.android.wm.shell.util.KtProtoLog
/**
* Limits the number of tasks shown in Desktop Mode.
@@ -42,9 +42,12 @@ class DesktopTasksLimiter (
private val shellTaskOrganizer: ShellTaskOrganizer,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
+ @VisibleForTesting
+ val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
init {
transitions.registerObserver(minimizeTransitionObserver)
+ taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
}
private data class TaskDetails (val displayId: Int, val taskId: Int)
@@ -68,7 +71,7 @@ class DesktopTasksLimiter (
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: task %d is not reordered to back nor invis",
taskToMinimize.taskId)
@@ -106,19 +109,48 @@ class DesktopTasksLimiter (
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: transition %s finished", transition)
mPendingTransitionTokensAndTasks.remove(transition)
}
}
+ @VisibleForTesting
+ inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener {
+ override fun onActiveTasksChanged(displayId: Int) {
+ val wct = WindowContainerTransaction()
+ removeLeftoverMinimizedTasks(displayId, wct)
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+
+ fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
+ if (taskRepository
+ .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) {
+ return
+ }
+ val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
+ if (remainingMinimizedTasks.isEmpty()) {
+ return
+ }
+ ProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksLimiter: removing leftover minimized tasks: $remainingMinimizedTasks")
+ remainingMinimizedTasks.forEach { taskIdToRemove ->
+ val taskToRemove = shellTaskOrganizer.getRunningTaskInfo(taskIdToRemove)
+ if (taskToRemove != null) {
+ wct.removeTask(taskToRemove.token)
+ }
+ }
+ }
+ }
+
/**
* Mark a task as minimized, this should only be done after the corresponding transition has
* finished so we don't minimize the task if the transition fails.
*/
private fun markTaskMinimized(displayId: Int, taskId: Int) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: marking %d as minimized", taskId)
taskRepository.minimizeTask(displayId, taskId)
@@ -137,7 +169,7 @@ class DesktopTasksLimiter (
wct: WindowContainerTransaction,
newFrontTaskInfo: RunningTaskInfo,
): RunningTaskInfo? {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
newFrontTaskInfo.taskId)
@@ -185,7 +217,7 @@ class DesktopTasksLimiter (
visibleFreeformTaskIdsOrderedFrontToBack: List<Int>
): RunningTaskInfo? {
if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: no need to minimize; tasks below limit")
// No need to minimize anything
@@ -195,7 +227,7 @@ class DesktopTasksLimiter (
shellTaskOrganizer.getRunningTaskInfo(
visibleFreeformTaskIdsOrderedFrontToBack.last())
if (taskToMinimize == null) {
- KtProtoLog.e(
+ ProtoLog.e(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DesktopTasksLimiter: taskToMinimize == null")
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index dae75f90e3ae..246fd9281975 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -21,35 +21,38 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+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.util.KtProtoLog
/**
- * A [Transitions.TransitionObserver] that observes shell transitions and updates
- * the [DesktopModeTaskRepository] state TODO: b/332682201
- * This observes transitions related to desktop mode
- * and other transitions that originate both within and outside shell.
+ * A [Transitions.TransitionObserver] that observes shell transitions and updates the
+ * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop
+ * mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
context: Context,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val transitions: Transitions,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
shellInit: ShellInit
) : Transitions.TransitionObserver {
init {
- if (Transitions.ENABLE_SHELL_TRANSITIONS &&
- DesktopModeStatus.canEnterDesktopMode(context)) {
+ if (
+ Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context)
+ ) {
shellInit.addInitCallback(::onInit, this)
}
}
fun onInit() {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
transitions.registerObserver(this)
}
@@ -83,8 +86,16 @@ class DesktopTasksTransitionObserver(
change.taskInfo?.let { taskInfo ->
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
when (change.mode) {
- WindowManager.TRANSIT_OPEN ->
+ WindowManager.TRANSIT_OPEN -> {
desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ // After the task for the wallpaper is created, set it non-trimmable.
+ // This is important to prevent recents from trimming and removing the
+ // task.
+ shellTaskOrganizer.applyTransaction(
+ WindowContainerTransaction()
+ .setTaskTrimmableFromRecents(taskInfo.token, false)
+ )
+ }
WindowManager.TRANSIT_CLOSE ->
desktopModeTaskRepository.wallpaperActivityToken = null
else -> {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index c4a4474689fa..1c2415c236ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -21,8 +21,8 @@ import android.app.ActivityManager
import android.content.ComponentName
import android.os.Bundle
import android.view.WindowManager
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.util.KtProtoLog
/**
* A transparent activity used in the desktop mode to show the wallpaper under the freeform windows.
@@ -36,7 +36,7 @@ import com.android.wm.shell.util.KtProtoLog
class DesktopWallpaperActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
- KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index d99b724c936f..ddee8fac8f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -29,6 +29,7 @@ import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -42,7 +43,6 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_D
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
-import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -114,7 +114,7 @@ class DragToDesktopTransitionHandler(
dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: Drag to desktop transition already in progress."
)
@@ -599,7 +599,7 @@ class DragToDesktopTransitionHandler(
) {
val state = transitionState ?: return
if (aborted && state.startTransitionToken == transition) {
- KtProtoLog.v(
+ ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"DragToDesktop: onTransitionConsumed() start transition aborted"
)
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 891f75cfdbda..171378f9a164 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
@@ -42,6 +42,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -60,6 +62,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
private final Context mContext;
private final Transitions mTransitions;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -67,17 +70,21 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context) {
- this(transitions, SurfaceControl.Transaction::new, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
- Context context) {
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
+ mInteractionJankMonitor = interactionJankMonitor;
}
/**
@@ -146,6 +153,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
final int screenHeight = metrics.heightPixels;
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
+ mInteractionJankMonitor
+ .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
@@ -175,6 +184,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
if (mOnAnimationFinishedCallback != null) {
mOnAnimationFinishedCallback.accept(finishT);
}
+ mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 5335c0b69a24..c35d77a1d74a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -28,7 +28,7 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.internal.jank.Cuj
-import com.android.wm.shell.common.InteractionJankMonitorUtils
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -37,7 +37,8 @@ import java.util.function.Supplier
/** Handles the animation of quick resizing of desktop tasks. */
class ToggleResizeDesktopTaskTransitionHandler(
private val transitions: Transitions,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val interactionJankMonitor: InteractionJankMonitor
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
@@ -46,8 +47,9 @@ class ToggleResizeDesktopTaskTransitionHandler(
private var boundsAnimator: Animator? = null
constructor(
- transitions: Transitions
- ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+ transitions: Transitions,
+ interactionJankMonitor: InteractionJankMonitor
+ ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/** Starts a quick resize transition. */
fun startTransition(wct: WindowContainerTransaction) {
@@ -105,7 +107,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
boundsAnimator = null
- InteractionJankMonitorUtils.endTracing(
+ interactionJankMonitor.end(
Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
}
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index b1cbe8d98397..84f6af4125b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -68,7 +68,7 @@ adb shell dumpsys SurfaceFlinger
## Tracing global SurfaceControl transaction updates
While Winscope traces are very useful, it sometimes doesn't give you enough information about which
-part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
stack traces when specific surface transaction calls are made, which is possible by enabling the
following system properties for example:
```shell
@@ -81,9 +81,11 @@ adb logcat -s "SurfaceControlRegistry"
# Disabling logging
adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
-adb reboot
```
+A reboot is required to enable the logging. Once enabled, reboot is not needed to update the
+properties.
+
It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
noisy if unfiltered.
@@ -97,7 +99,7 @@ adb reboot
adb logcat -s "SurfaceControlRegistry"
```
-## Tracing activity starts in the app process
+## Tracing activity starts & finishes in the app process
It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
@@ -113,6 +115,19 @@ adb shell setprop persist.wm.debug.start_activity \"\"
adb reboot
```
+Likewise, to trace where a finish() call may be made in the app process, you can enable this system
+property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.finish_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.finish_activity \"\"
+adb reboot
+```
+
## Dumps
Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index b3c3a3dcf272..e00353d6ac82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -52,6 +52,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -301,7 +302,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
break;
}
}
- if (pd == null || !pd.isHandlingDrag) {
+ if (pd == null || pd.activeDragCount <= 0 || !pd.isHandlingDrag) {
// Not currently dragging
return;
}
@@ -332,9 +333,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
+ "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
pd.isHandlingDrag, event.getClipData().getItemCount(),
- DragUtils.getMimeTypesConcatenated(description));
+ DragUtils.getMimeTypesConcatenated(description),
+ DragUtils.dragFlagsToString(event.getDragFlags()));
}
if (!pd.isHandlingDrag) {
@@ -353,6 +355,12 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
pd.dragSession.initialize();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Hiding task surface: taskId=%d", pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, false /* visible */);
+ }
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners(l -> {
l.onDragStarted();
@@ -382,6 +390,13 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
if (pd.dragLayout.hasDropped()) {
mLogger.logDrop();
} else {
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Re-showing task surface: taskId=%d",
+ pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, true /* visible */);
+ }
pd.activeDragCount--;
pd.dragLayout.hide(event, () -> {
if (pd.activeDragCount == 0) {
@@ -435,7 +450,16 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private boolean handleDrop(DragEvent event, PerDisplay pd) {
final SurfaceControl dragSurface = event.getDragSurface();
pd.activeDragCount--;
- return pd.dragLayout.drop(event, dragSurface, () -> {
+ // Find the token of the task to hide as a part of entering split
+ WindowContainerToken hideTaskToken = null;
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ActivityManager.RunningTaskInfo info = mShellTaskOrganizer.getRunningTaskInfo(
+ pd.dragSession.hideDragSourceTaskId);
+ if (info != null) {
+ hideTaskToken = info.token;
+ }
+ }
+ return pd.dragLayout.drop(event, dragSurface, hideTaskToken, () -> {
if (pd.activeDragCount == 0) {
// Hide the window if another drag hasn't been started while animating the drop
setDropTargetWindowVisibility(pd, View.INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9c7476d1a1b0..95fe8b6f1f4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -59,6 +59,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.window.WindowContainerToken;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -234,8 +235,13 @@ public class DragAndDropPolicy {
return null;
}
+ /**
+ * Handles the drop on a given {@param target}. If a {@param hideTaskToken} is set, then the
+ * handling of the drop will attempt to hide the given task as a part of the same window
+ * container transaction if possible.
+ */
@VisibleForTesting
- void handleDrop(Target target) {
+ void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -254,16 +260,17 @@ public class DragAndDropPolicy {
? mFullscreenStarter
: mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, starter, position);
+ launchApp(mSession, starter, position, hideTaskToken);
} else {
- launchIntent(mSession, starter, position);
+ launchIntent(mSession, starter, position, hideTaskToken);
}
}
/**
* Launches an app provided by SysUI.
*/
- private void launchApp(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
position);
final ClipDescription description = session.getClipDescription();
@@ -283,8 +290,12 @@ public class DragAndDropPolicy {
if (isTask) {
final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
- starter.startTask(taskId, position, opts);
+ starter.startTask(taskId, position, opts, hideTaskToken);
} else if (isShortcut) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Can not hide task token with starting shortcut");
+ }
final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
starter.startShortcut(packageName, id, position, opts, user);
@@ -297,14 +308,15 @@ public class DragAndDropPolicy {
}
}
starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
- position, opts);
+ position, opts, hideTaskToken);
}
}
/**
* Launches an intent sender provided by an application.
*/
- private void launchIntent(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -319,18 +331,20 @@ public class DragAndDropPolicy {
final Bundle opts = baseActivityOpts.toBundle();
starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
- null /* fillIntent */, position, opts);
+ null /* fillIntent */, position, opts, hideTaskToken);
}
/**
* Interface for actually committing the task launches.
*/
public interface Starter {
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options);
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options);
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -352,7 +366,12 @@ public class DragAndDropPolicy {
}
@Override
- public void startTask(int taskId, int position, @Nullable Bundle options) {
+ public void startTask(int taskId, int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
@@ -375,7 +394,12 @@ public class DragAndDropPolicy {
@Override
public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- int position, @Nullable Bundle options) {
+ int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 910175ef3023..e4aa115347eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -51,8 +51,10 @@ import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
@@ -291,6 +293,8 @@ public class DragLayout extends LinearLayout
int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
+ mDropZoneView1.setForceIgnoreBottomMargin(false);
+ mDropZoneView2.setForceIgnoreBottomMargin(false);
updateDropZoneSizes(null, null); // passing null splits the views evenly
} else {
// We use the first drop zone to show the fullscreen highlight, and don't need
@@ -483,13 +487,13 @@ public class DragLayout extends LinearLayout
/**
* Handles the drop onto a target and animates out the visible drop targets.
*/
- public boolean drop(DragEvent event, SurfaceControl dragSurface,
- Runnable dropCompleteCallback) {
+ public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
+ @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
final boolean handledDrop = mCurrentTarget != null;
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget);
+ mPolicy.handleDrop(mCurrentTarget, hideTaskToken);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -499,7 +503,7 @@ public class DragLayout extends LinearLayout
return handledDrop;
}
- private void hideDragSurface(SurfaceControl dragSurface) {
+ private void hideDragSurface(@NonNull SurfaceControl dragSurface) {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
// Currently the splash icon animation runs with the default ValueAnimator duration of
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 3bedef21184e..dcbdfa349687 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -27,6 +28,7 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.PersistableBundle;
import androidx.annotation.Nullable;
@@ -63,6 +65,7 @@ public class DragSession {
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
+ int hideDragSourceTaskId = -1;
DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data, int dragFlags) {
@@ -70,6 +73,11 @@ public class DragSession {
mInitialDragData = data;
mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
+ hideDragSourceTaskId = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
+ : -1;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Extracting drag source taskId: taskId=%d", hideDragSourceTaskId);
}
/**
@@ -84,16 +92,27 @@ public class DragSession {
* Updates the running task for this drag session.
*/
void updateRunningTask() {
+ final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
final List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
+ false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Running task: id=%d component=%s", task.taskId,
- task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Skipping running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ continue;
+ }
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ break;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index e215870f1894..22cfa328bfda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -19,16 +19,28 @@ package com.android.wm.shell.draganddrop;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.view.View.DRAG_FLAG_ACCESSIBILITY_ACTION;
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_READ;
+import static android.view.View.DRAG_FLAG_GLOBAL_URI_WRITE;
+import static android.view.View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START;
+import static android.view.View.DRAG_FLAG_OPAQUE;
+import static android.view.View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION;
+import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.StringJoiner;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -76,7 +88,7 @@ public class DragUtils {
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) {
- if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
+ if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) {
// Disallow launching the intent if the app does not want to delegate it to the system
return null;
}
@@ -105,4 +117,35 @@ public class DragUtils {
}
return mimeTypes;
}
+
+ /**
+ * Returns the string description of the given {@param dragFlags}.
+ */
+ public static String dragFlagsToString(int dragFlags) {
+ StringJoiner str = new StringJoiner("|");
+ if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) {
+ str.add("GLOBAL");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
+ str.add("GLOBAL_URI_READ");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
+ str.add("GLOBAL_URI_WRITE");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PERSISTABLE_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
+ str.add("GLOBAL_PREFIX_URI_PERMISSION");
+ } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
+ str.add("OPAQUE");
+ } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ str.add("ACCESSIBILITY_ACTION");
+ } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
+ str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION");
+ } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
+ str.add("GLOBAL_SAME_APPLICATION");
+ } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG");
+ } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ str.add("HIDE_CALLING_TASK_ON_DRAG_START");
+ }
+ return str.toString();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index b6b49a87484e..18cd2d84d32d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -151,6 +151,10 @@ public class DropZoneView extends FrameLayout {
/** Ignores the bottom margin provided by the insets. */
public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "setForceIgnoreBottomMargin: ignore=%b", ignoreBottomMargin);
+ }
mIgnoreBottomMargin = ignoreBottomMargin;
if (mMarginPercent > 0) {
mMarginView.invalidate();
@@ -159,8 +163,14 @@ public class DropZoneView extends FrameLayout {
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setBottomInset: inset=%f",
+ bottom);
+ }
mBottomInset = bottom;
- ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
+ final LayoutParams lp = (LayoutParams) mSplashScreenView.getLayoutParams();
+ lp.bottomMargin = (int) bottom;
+ mSplashScreenView.setLayoutParams(lp);
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
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 1641668a98a1..4531967d6f93 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
@@ -29,7 +29,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
+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;
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 dc449d1aaf74..a8346a9b3b48 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -625,6 +626,14 @@ public class PipAnimationController {
}
} else {
adjustedSourceRectHint.set(sourceRectHint);
+ if (isInPipDirection(direction)
+ && rotationDelta == ROTATION_0
+ && taskInfo.displayCutoutInsets != null) {
+ // TODO: this is to special case the issues on Foldable device
+ // with display cutout. This aligns with what's in SwipePipToHomeAnimator.
+ adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
}
final Rect sourceHintRectInsets = new Rect();
if (!adjustedSourceRectHint.isEmpty()) {
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 789f7068c0a9..6e1e44b7ede0 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
@@ -496,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
+ }
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
@@ -636,6 +638,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
+ // bail early if leash is null
+ if (mLeash == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "exitPip: leash is null");
+ return;
+ }
+
final Rect destinationBounds = new Rect(getExitDestinationBounds());
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
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 e5633de2a3a2..87692ac52908 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
@@ -1174,6 +1174,7 @@ public class PipTransition extends PipTransitionController {
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index ccbe94c4c780..8d36db97c511 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
@@ -127,8 +127,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
/**
* Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param duration the suggested duration for resize animation.
*/
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
// Default implementation does nothing.
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d1d82755d12c..0cb7e17e41e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -374,7 +374,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
- public static Pip create(Context context,
+ public static PipImpl create(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -1177,7 +1177,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/**
* The interface for calls from outside the Shell, within the host process.
*/
- private class PipImpl implements Pip {
+ public class PipImpl implements Pip {
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
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 5c561fed89c7..88f9e4c740e3 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
@@ -47,9 +47,17 @@ public class PipResizeAnimator extends ValueAnimator
@Nullable
private Runnable mAnimationEndCallback;
private RectEvaluator mRectEvaluator;
+
+ // Bounds relative to which scaling/cropping must be done.
private final Rect mBaseBounds = new Rect();
+
+ // Bounds to animate from.
private final Rect mStartBounds = new Rect();
+
+ // Target bounds.
private final Rect mEndBounds = new Rect();
+
+ // Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
private final float mDelta;
@@ -84,7 +92,6 @@ public class PipResizeAnimator extends ValueAnimator
addListener(this);
addUpdateListener(this);
setEvaluator(mRectEvaluator);
- // TODO: change this
setDuration(duration);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 68202ef642c4..c12219c8a9e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -55,6 +55,8 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -62,6 +64,8 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
@@ -86,6 +90,8 @@ public class PipController implements ConfigurationChangeListener,
private final ShellTaskOrganizer mShellTaskOrganizer;
private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
+ private final PipImpl mImpl;
+ private Consumer<Boolean> mOnIsInPipStateChangedListener;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
private PipAnimationListener mPipRecentsAnimationListener;
@@ -140,6 +146,7 @@ public class PipController implements ConfigurationChangeListener,
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
+ mImpl = new PipImpl();
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
@@ -174,6 +181,10 @@ public class PipController implements ConfigurationChangeListener,
pipTransitionState, mainExecutor);
}
+ public PipImpl getPipImpl() {
+ return mImpl;
+ }
+
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
// Ensure that we have the display info in case we get calls to update the bounds before the
@@ -310,22 +321,29 @@ public class PipController implements ConfigurationChangeListener,
@Override
public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
- if (newState == PipTransitionState.SWIPING_TO_PIP) {
- Preconditions.checkState(extra != null,
- "No extra bundle for " + mPipTransitionState);
-
- SurfaceControl overlay = extra.getParcelable(
- SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
- Rect appBounds = extra.getParcelable(
- SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
-
- Preconditions.checkState(appBounds != null,
- "App bounds can't be null for " + mPipTransitionState);
- mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
- } else if (newState == PipTransitionState.ENTERED_PIP) {
- if (mPipTransitionState.isInSwipePipToHomeTransition()) {
- mPipTransitionState.resetSwipePipToHomeState();
- }
+ switch (newState) {
+ case PipTransitionState.SWIPING_TO_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
+
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ break;
+ case PipTransitionState.ENTERED_PIP:
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ break;
}
}
@@ -352,6 +370,54 @@ public class PipController implements ConfigurationChangeListener,
mPipBoundsAlgorithm.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipDisplayLayoutState.dump(pw, innerPrefix);
+ mPipTransitionState.dump(pw, innerPrefix);
+ }
+
+ private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mOnIsInPipStateChangedListener = callback;
+ if (mOnIsInPipStateChangedListener != null) {
+ callback.accept(mPipTransitionState.isInPip());
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ public class PipImpl implements Pip {
+ @Override
+ public void expandPip() {}
+
+ @Override
+ public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
+
+ @Override
+ public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.setOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.removePipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void showPictureInPictureMenu() {}
+
+ @Override
+ public void registerPipTransitionCallback(
+ PipTransitionController.PipTransitionCallback callback,
+ Executor executor) {}
}
/**
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 cec246923e2c..ea02de9d9704 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
@@ -25,6 +26,7 @@ import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +38,7 @@ import android.os.Debug;
import android.view.SurfaceControl;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -45,6 +48,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
@@ -62,6 +66,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipMotionHelper";
private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
+ private static final String ANIMATING_BOUNDS_CHANGE = "animating_bounds_change";
private static final boolean DEBUG = false;
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
@@ -113,7 +118,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** SpringConfig to use for fling-then-spring animations. */
private final PhysicsAnimator.SpringConfig mSpringConfig =
- new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+ new PhysicsAnimator.SpringConfig(300f, DAMPING_RATIO_LOW_BOUNCY);
/** SpringConfig used for animating into the dismiss region, matches the one in
* {@link MagnetizedObject}. */
@@ -152,9 +157,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private boolean mDismissalPending = false;
/**
- * Set to true if bounds change transition has been scheduled from PipMotionHelper.
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper
+ * after animating is over.
*/
- private boolean mWaitingForBoundsChangeTransition = false;
+ private boolean mWaitingForFlingTransition = false;
+
+ /**
+ * Set to true if bounds change transition has been scheduled from PipMotionHelper,
+ * and if the animation is supposed to run while transition is playing.
+ */
+ private boolean mWaitingToPlayBoundsChangeTransition = false;
/**
* Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
@@ -634,6 +646,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target.
if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // Update the earlier estimate on bounds we are animating towards, since physics
+ // animator is non-deterministic.
+ setAnimatingToBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
// do not schedule resize if PiP is dismissing, which may cause app re-open to
// mBounds instead of its normal bounds.
Bundle extra = new Bundle();
@@ -673,6 +688,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Directly resizes the PiP to the given {@param bounds}.
*/
private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ // Do not carry out any resizing if we are dragging or physics animator is running.
+ return;
+ }
+
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: resizeAndAnimatePipUnchecked: toBounds=%s"
@@ -682,10 +702,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
// Intentionally resize here even if the current bounds match the destination bounds.
// This is so all the proper callbacks are performed.
-
- // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
- // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
- // setAnimatingToBounds(toBounds);
+ setAnimatingToBounds(toBounds);
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, duration);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
}
@Override
@@ -694,7 +715,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
@Nullable Bundle extra) {
switch (newState) {
case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
- if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+ mWaitingForFlingTransition = extra.getBoolean(FLING_BOUNDS_CHANGE);
+ mWaitingToPlayBoundsChangeTransition = extra.getBoolean(ANIMATING_BOUNDS_CHANGE);
+ if (!mWaitingForFlingTransition && !mWaitingToPlayBoundsChangeTransition) {
+ break;
+ }
if (mPipBoundsState.getBounds().equals(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
@@ -709,30 +734,30 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
break;
}
- // If touch is turned off and we are in a fling animation, schedule a transition.
- mWaitingForBoundsChangeTransition = true;
+ // Delay config until the end, if we are animating after scheduling the transition.
mPipScheduler.scheduleAnimateResizePip(
- mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().getAnimatingToBounds(),
+ mWaitingToPlayBoundsChangeTransition,
+ extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION));
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
- if (!mWaitingForBoundsChangeTransition) break;
-
- // If bounds change transition was scheduled from this class, handle leash updates.
- mWaitingForBoundsChangeTransition = false;
SurfaceControl.Transaction startTx = extra.getParcelable(
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishTx = extra.getParcelable(
+ PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
Rect destinationBounds = extra.getParcelable(
PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
- startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
- destinationBounds.left, destinationBounds.top);
- startTx.apply();
-
- // All motion operations have actually finished, so make bounds cache updates.
- settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
- cleanUpHighPerfSessionMaybe();
-
- // Signal that the transition is done - should update transition state by default.
- mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
+ if (mWaitingForFlingTransition) {
+ mWaitingForFlingTransition = false;
+ handleFlingTransition(startTx, finishTx, destinationBounds);
+ } else if (mWaitingToPlayBoundsChangeTransition) {
+ mWaitingToPlayBoundsChangeTransition = false;
+ startResizeAnimation(startTx, finishTx, destinationBounds, duration);
+ }
break;
case PipTransitionState.EXITING_PIP:
// We need to force finish any local animators if about to leave PiP, to avoid
@@ -740,9 +765,46 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
cancelPhysicsAnimation();
settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ break;
}
}
+ private void handleFlingTransition(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds) {
+ startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+ destinationBounds.left, destinationBounds.top);
+ startTx.apply();
+
+ // All motion operations have actually finished, so make bounds cache updates.
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+ cleanUpHighPerfSessionMaybe();
+
+ // Signal that the transition is done - should update transition state by default.
+ mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+ }
+
+ private void startResizeAnimation(SurfaceControl.Transaction startTx,
+ SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
+ SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ 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(),
+ destinationBounds, duration, 0f /* angle */);
+ animator.setAnimationEndCallback(() -> {
+ mPipBoundsState.setBounds(destinationBounds);
+ // All motion operations have actually finished, so make bounds cache updates.
+ cleanUpHighPerfSessionMaybe();
+ // Signal that we are done with resize transition
+ mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+ });
+ animator.start();
+ }
+
private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
if (!animatingAfter) {
// The physics animation ended, though we may not necessarily be done animating, such as
@@ -754,6 +816,26 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
+
+ // Check whether new bounds after fling imply we need to update stash state too.
+ stashEndActionIfNeeded();
+ }
+
+ private void stashEndActionIfNeeded() {
+ boolean isStashing = mPipBoundsState.getBounds().right > mPipBoundsState
+ .getDisplayBounds().width() || mPipBoundsState.getBounds().left < 0;
+ if (!isStashing) {
+ return;
+ }
+
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
}
/**
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 33e80bd80988..5b0ca1837a1c 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
import android.annotation.Nullable;
import android.content.Context;
@@ -535,7 +536,8 @@ public class PipResizeGestureHandler implements
mWaitingForBoundsChangeTransition = true;
// Schedule PiP resize transition, but delay any config updates until very end.
- mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
+ mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds,
+ true /* configAtEnd */, PINCH_RESIZE_SNAP_DURATION);
break;
case PipTransitionState.CHANGING_PIP_BOUNDS:
if (!mWaitingForBoundsChangeTransition) break;
@@ -550,12 +552,15 @@ public class PipResizeGestureHandler implements
PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishTx = extra.getParcelable(
PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.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, PINCH_RESIZE_SNAP_DURATION, mAngle);
+ mLastResizeBounds, duration, mAngle);
animator.setAnimationEndCallback(() -> {
// All motion operations have actually finished, so make bounds cache updates.
mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
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 4f62192eaf5b..ac670cf3f828 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
@@ -162,6 +162,18 @@ public class PipScheduler {
* @param configAtEnd true if we are delaying config updates until the transition ends.
*/
public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) {
+ scheduleAnimateResizePip(toBounds, configAtEnd,
+ PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+ }
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ *
+ * @param configAtEnd true if we are delaying config updates until the transition ends.
+ * @param duration the suggested duration to run the animation; the component responsible
+ * for running the animator will get this as an extra.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) {
if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
return;
}
@@ -170,7 +182,7 @@ public class PipScheduler {
if (configAtEnd) {
wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
}
- mPipTransitionController.startResizeTransition(wct);
+ mPipTransitionController.startResizeTransition(wct, duration);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index a9f3b54006d9..53b80e8b7542 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -106,9 +106,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
private float mStashVelocityThreshold;
- // The reference inset bounds, used to determine the dismiss fraction
- private final Rect mInsetBounds = new Rect();
-
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds
private int mDeferResizeToNormalBoundsUntilRotation = -1;
@@ -206,17 +203,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
- if (mPipBoundsState.isStashed()) {
- animateToUnStashedState();
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
- } else {
- mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
- mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
- willResizeMenu(),
- shouldShowResizeHandle());
- }
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
},
menuController::hideMenu,
mainExecutor);
@@ -438,7 +428,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
mDisplayRotation = displayRotation;
- mInsetBounds.set(insetBounds);
updateMovementBounds();
mMovementBoundsExtraOffsets = extraOffset;
@@ -713,15 +702,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
}
}
- private void animateToMaximizedState(Runnable callback) {
- Rect maxMovementBounds = new Rect();
+ private void animateToMaximizedState() {
Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
mPipBoundsState.getMaxSize().y);
- mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
- mIsImeShowing ? mImeHeight : 0);
+
mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
- mPipBoundsState.getMovementBounds(), maxMovementBounds,
- callback);
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(maxBounds), null /* callback */);
}
private void animateToNormalSize(Runnable callback) {
@@ -729,22 +716,20 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
- final Rect normalBounds = mPipBoundsState.getNormalBounds();
- final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
- minMenuSize);
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(destBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
- mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
+ final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+ final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
+ normalBounds, minMenuSize);
+
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(adjustedNormalBounds), callback /* callback */);
}
private void animateToUnexpandedState(Rect restoreBounds) {
- Rect restoredMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
- restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ getMovementBounds(restoreBounds),
+ getMovementBounds(mPipBoundsState.getBounds()), false /* immediate */);
mSavedSnapFraction = -1f;
}
@@ -752,10 +737,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
final Rect pipBounds = mPipBoundsState.getBounds();
final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
- unStashedBounds.left = onLeftEdge ? mInsetBounds.left
- : mInsetBounds.right - pipBounds.width();
- unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
- : mInsetBounds.right;
+
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ unStashedBounds.left = onLeftEdge ? insetBounds.left
+ : insetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? insetBounds.left + pipBounds.width()
+ : insetBounds.right;
mMotionHelper.animateToUnStashedBounds(unStashedBounds);
}
@@ -903,8 +891,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// Reset the touch state on up before the fling settles
mTouchState.reset();
if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
- // mMotionHelper.stashToEdge(vel.x, vel.y,
- // this::stashEndAction /* endAction */);
+ mMotionHelper.stashToEdge(vel.x, vel.y, null /* endAction */);
} else {
if (mPipBoundsState.isStashed()) {
// Reset stashed state if previously stashed
@@ -919,10 +906,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
&& mMenuState != MENU_STATE_FULL) {
// If using pinch to zoom, double-tap functions as resizing between max/min size
if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- final boolean toExpand = mPipBoundsState.getBounds().width()
- < mPipBoundsState.getMaxSize().x
- && mPipBoundsState.getBounds().height()
- < mPipBoundsState.getMaxSize().y;
if (mMenuController.isMenuVisible()) {
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
@@ -934,7 +917,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// actually toggle to the size chosen
if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
- animateToMaximizedState(null);
+ animateToMaximizedState();
} else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToNormalSize(null);
@@ -1038,14 +1021,18 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
* resized.
*/
private void updateMovementBounds() {
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
- mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
}
private Rect getMovementBounds(Rect curBounds) {
Rect movementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ Rect insetBounds = new Rect();
+ mPipBoundsAlgorithm.getInsetBounds(insetBounds);
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
movementBounds, mIsImeShowing ? mImeHeight : 0);
return movementBounds;
}
@@ -1091,6 +1078,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
case PipTransitionState.ENTERED_PIP:
onActivityPinned();
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
case PipTransitionState.EXITED_PIP:
mTouchState.setAllowInputEvents(false);
@@ -1101,6 +1089,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
break;
case PipTransitionState.CHANGED_PIP_BOUNDS:
mTouchState.setAllowInputEvents(true);
+ mTouchState.reset();
break;
}
}
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 57dc5f92b2b6..683d30d59b33 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
@@ -71,6 +71,9 @@ public class PipTransition extends PipTransitionController implements
static final String PIP_START_TX = "pip_start_tx";
static final String PIP_FINISH_TX = "pip_finish_tx";
static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
+ static final String ANIMATING_BOUNDS_CHANGE_DURATION =
+ "animating_bounds_change_duration";
+ static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -87,7 +90,7 @@ public class PipTransition extends PipTransitionController implements
private final PipTransitionState mPipTransitionState;
//
- // Transition tokens
+ // Transition caches
//
@Nullable
@@ -96,6 +99,8 @@ public class PipTransition extends PipTransitionController implements
private IBinder mExitViaExpandTransition;
@Nullable
private IBinder mResizeTransition;
+ private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+
//
// Internal state and relevant cached info
@@ -152,11 +157,12 @@ public class PipTransition extends PipTransitionController implements
}
@Override
- public void startResizeTransition(WindowContainerTransaction wct) {
+ public void startResizeTransition(WindowContainerTransaction wct, int duration) {
if (wct == null) {
return;
}
mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mBoundsChangeDuration = duration;
}
@Nullable
@@ -272,6 +278,10 @@ public class PipTransition extends PipTransitionController implements
extra.putParcelable(PIP_START_TX, startTransaction);
extra.putParcelable(PIP_FINISH_TX, finishTransaction);
extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
+ if (mBoundsChangeDuration > BOUNDS_CHANGE_JUMPCUT_DURATION) {
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, mBoundsChangeDuration);
+ mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+ }
mFinishCallback = finishCallback;
mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 9d599caf13dd..29272be6e9bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -62,6 +63,8 @@ import java.util.List;
* and throw an <code>IllegalStateException</code> otherwise.</p>
*/
public class PipTransitionState {
+ private static final String TAG = PipTransitionState.class.getSimpleName();
+
public static final int UNDEFINED = 0;
// State for Launcher animating the swipe PiP to home animation.
@@ -190,8 +193,9 @@ public class PipTransitionState {
"No extra bundle for " + stateToString(state) + " state.");
}
if (mState != state) {
- dispatchPipTransitionStateChanged(mState, state, extra);
+ final int prevState = mState;
mState = state;
+ dispatchPipTransitionStateChanged(prevState, mState, extra);
}
}
@@ -319,4 +323,11 @@ public class PipTransitionState {
return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
stateToString(mState), mInSwipePipToHomeTransition);
}
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mState=" + stateToString(mState));
+ }
}
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 814eaae1ea74..9539a456502f 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
@@ -37,6 +37,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.IRecentsAnimationRunner;
+import android.window.WindowContainerToken;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -52,9 +53,9 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -457,11 +458,31 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
/**
- * Find the background task that match the given component.
+ * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
+ * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask(
+ @Nullable WindowContainerToken ignoreTaskToken) {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2,
+ false /* filterOnlyVisibleRecents */);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
+ return task;
+ }
+ return null;
+ }
+
+ /**
+ * Find the background task that match the given component. Ignores tasks match
+ * {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
- int userId) {
+ int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
@@ -473,6 +494,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (task.isVisible) {
continue;
}
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
return task;
}
@@ -640,7 +664,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
@Override
public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) {
final ActivityManager.RunningTaskInfo[][] tasks =
- new ActivityManager.RunningTaskInfo[][] {null};
+ new ActivityManager.RunningTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRunningTasks",
(controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum)
.toArray(new ActivityManager.RunningTaskInfo[0]),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index e46625debcc6..234b4d0f86db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -74,7 +74,6 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.function.Consumer;
-import java.util.function.Supplier;
/**
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
@@ -85,7 +84,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private final Transitions mTransitions;
private final ShellExecutor mExecutor;
- private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@Nullable
private final RecentTasksController mRecentTasksController;
private IApplicationThread mAnimApp = null;
@@ -103,13 +101,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
@Nullable RecentTasksController recentTasksController,
- HomeTransitionObserver homeTransitionObserver,
- Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ HomeTransitionObserver homeTransitionObserver) {
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
mHomeTransitionObserver = homeTransitionObserver;
- mTransactionSupplier = transactionSupplier;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
shellInit.addInitCallback(() -> {
@@ -1060,7 +1056,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
final Transitions.TransitionFinishCallback finishCB = mFinishCB;
mFinishCB = null;
- SurfaceControl.Transaction t = mFinishTransaction;
+ final SurfaceControl.Transaction t = mFinishTransaction;
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mKeyguardLocked && mRecentsTask != null) {
@@ -1110,16 +1106,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
- if (toHome && !mOpeningTasks.isEmpty()) {
- // Attempting to start a task after swipe to home, don't show it,
- // move recents to top
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " attempting to start a task after swipe to home");
- t = mTransactionSupplier.get();
- wct.reorder(mRecentsTask, true /*onTop*/);
- mClosingTasks.addAll(mOpeningTasks);
- mOpeningTasks.clear();
- }
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
t.show(mOpeningTasks.get(i).mTaskSurface);
@@ -1188,10 +1174,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mPipTransaction = null;
}
}
- if (t != mFinishTransaction) {
- // apply after merges because these changes are accounting for finishWCT changes.
- mTransitions.setAfterMergeFinishTransaction(mTransition, t);
- }
cleanUp();
finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
if (runnerFinishCb != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index b4941a5b49b5..e659151fee7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -64,6 +64,7 @@ import android.view.SurfaceSession;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -526,7 +527,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
}
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Starts an existing task into split.
+ * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead
+ * @param hideTaskToken is not supported.
+ */
+ public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Legacy startTask does not support hide task token");
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
@@ -584,8 +593,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
- user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
+ user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) {
if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
@@ -676,10 +685,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
* See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
- public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+ public void startIntentWithInstanceId(PendingIntent intent, int userId,
+ @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
+ @NonNull InstanceId instanceId) {
mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, userId, fillInIntent, position, options);
+ startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
}
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@@ -825,9 +835,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
+ /**
+ * Starts the given intent into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -838,23 +854,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
final String packageName1 = SplitScreenUtils.getPackageName(intent);
- final String packageName2 = getPackageName(reverseSplitPosition(position));
- final int userId2 = getUserId(reverseSplitPosition(position));
+ final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
+ final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
final ComponentName component = intent.getIntent().getComponent();
// To prevent accumulating large number of instances in the background, reuse task
// in the background. If we don't explicitly reuse, new may be created even if the app
// isn't multi-instance because WM won't automatically remove/reuse the previous instance
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
+ hideTaskToken))
.orElse(null);
if (taskInfo != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startTask(taskInfo.taskId, position, options);
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
} else {
- startTask(taskInfo.taskId, position, options);
+ startTask(taskInfo.taskId, position, options, hideTaskToken);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
@@ -879,19 +896,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
}
- mStageCoordinator.startIntent(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken);
}
- /** Retrieve package name of a specific split position if split screen is activated, otherwise
- * returns the package name of the top running task. */
+ /**
+ * Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task.
+ * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls
+ */
@Nullable
- private String getPackageName(@SplitPosition int position) {
+ private String getPackageName(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return null;
@@ -901,15 +922,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
- /** Retrieve user id of a specific split position if split screen is activated, otherwise
- * returns the user id of the top running task. */
- private int getUserId(@SplitPosition int position) {
+ /**
+ * Retrieve user id of a specific split position if split screen is activated, otherwise
+ * returns the user id of the top running task.
+ * TODO: Merge this with getPackageName() so we don't make multiple binder calls
+ */
+ private int getUserId(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return -1;
@@ -1290,7 +1315,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> controller.startTask(taskId, position, options));
+ (controller) -> controller.startTask(taskId, position, options,
+ null /* hideTaskToken */));
}
@Override
@@ -1402,8 +1428,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> controller.startIntent(intent, userId, fillInIntent, position,
- options, instanceId));
+ (controller) -> controller.startIntentWithInstanceId(intent, userId,
+ fillInIntent, position, options, instanceId));
}
@Override
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 d9e97766e4fe..41042344fd3a 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
@@ -592,12 +592,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- /** Use this method to launch an existing Task via a taskId */
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Use this method to launch an existing Task via a taskId.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
@@ -623,9 +632,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
extraTransitType, !mIsDropEntering);
}
- /** Launches an activity into split. */
+ /**
+ * Launches an activity into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
+ @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
@@ -636,6 +649,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 6325c686a682..ea8c0eb30061 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,8 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
+ mFromYDelta = firstWindowFrame.top
+ - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 3a680097554f..dd4595a70211 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -75,6 +76,7 @@ public class ShellInit {
*/
@VisibleForTesting
public void init() {
+ ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index a126cbe41b00..9750d3ec99f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -535,7 +535,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */,
+ 0 /* flags */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
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 73b32a24246a..778478405dda 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -40,6 +41,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECI
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -62,6 +64,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.isCoveredByOpaqueFullscreenChange;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
@@ -352,6 +355,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
final int mode = change.getMode();
boolean isSeamlessDisplayChange = false;
@@ -458,6 +462,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int layer = zSplitLine + numChanges - i;
startTransaction.setLayer(change.getLeash(), layer);
}
+ } else if (!isCoveredByOpaqueFullscreenChange(info, change)
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Reparent the minimize-change to the root task so the minimizing Task
+ // isn't shown in front of other Tasks.
+ mRootTDAOrganizer.reparentToDisplayArea(
+ change.getTaskInfo().displayId,
+ change.getLeash(),
+ startTransaction);
} else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
&& TransitionUtil.isClosingType(mode)) {
// If there is a closing translucent task in an OPENING transition, we will
@@ -985,7 +999,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int animType = options.getType();
return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
|| animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
- || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
+ || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS
+ || animType == ANIM_FROM_STYLE;
}
private static void applyTransformation(long time, SurfaceControl.Transaction t,
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 a5f071af6973..75e7ddf53f9f 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
@@ -36,6 +36,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.WindowConfiguration;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -72,6 +73,9 @@ public class TransitionAnimationHelper {
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
+ final boolean isCoveredByOpaqueFullscreenChange =
+ isCoveredByOpaqueFullscreenChange(info, change);
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
@@ -107,6 +111,24 @@ public class TransitionAnimationHelper {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // The main Task is launching or being brought to front, this Task is being minimized
+ animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && type == TRANSIT_TO_FRONT
+ && change.getMode() == TRANSIT_TO_FRONT) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // Bring the minimized Task back to front
+ animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation;
} else if (type == TRANSIT_OPEN) {
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
@@ -417,4 +439,25 @@ public class TransitionAnimationHelper {
return edgeExtensionLayer;
}
+
+ /**
+ * Returns whether there is an opaque fullscreen Change positioned in front of the given Change
+ * in the given TransitionInfo.
+ */
+ static boolean isCoveredByOpaqueFullscreenChange(
+ TransitionInfo info, TransitionInfo.Change change) {
+ // TransitionInfo#getChanges() are ordered from front to back
+ for (TransitionInfo.Change coveringChange : info.getChanges()) {
+ if (coveringChange == change) {
+ return false;
+ }
+ if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0
+ && coveringChange.getTaskInfo() != null
+ && coveringChange.getTaskInfo().getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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 ec6802da85f6..8d53bebba7bb 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
@@ -28,14 +28,17 @@ 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;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.window.flags.Flags.enforceShellThreadModel;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
@@ -76,6 +79,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -241,13 +245,6 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Ordered list of transitions which have been merged into this one. */
private ArrayList<ActiveTransition> mMerged;
- /**
- * @deprecated DO NOT USE THIS unless absolutely necessary. It will be removed once
- * everything migrates off finishWCT.
- */
- @java.lang.Deprecated
- SurfaceControl.Transaction mAfterMergeFinishT;
-
ActiveTransition(IBinder token) {
mToken = token;
}
@@ -585,6 +582,14 @@ public class Transitions implements RemoteCallable<Transitions>,
final boolean isOpening = isOpeningType(transitType);
final boolean isClosing = isClosingType(transitType);
final int mode = change.getMode();
+ // Ensure wallpapers stay in the back
+ if (change.hasFlags(FLAG_IS_WALLPAPER) && Flags.ensureWallpaperInTransitions()) {
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ return -zSplitLine + numChanges - i;
+ } else {
+ return -zSplitLine - i;
+ }
+ }
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
@@ -803,14 +808,16 @@ public class Transitions implements RemoteCallable<Transitions>,
final int changeSize = info.getChanges().size();
boolean taskChange = false;
boolean transferStartingWindow = false;
- int noAnimationBehindStartingWindow = 0;
+ int animBehindStartingWindow = 0;
boolean allOccluded = changeSize > 0;
for (int i = changeSize - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
taskChange |= change.getTaskInfo() != null;
transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
- if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
- noAnimationBehindStartingWindow++;
+ if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)
+ || change.hasAllFlags(
+ FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ animBehindStartingWindow++;
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
@@ -828,11 +835,11 @@ public class Transitions implements RemoteCallable<Transitions>,
// There does not need animation when:
// A. Transfer starting window. Apply transfer starting window directly if there is no other
// task change. Since this is an activity->activity situation, we can detect it by selecting
- // transitions with only 2 changes where
- // 1. neither are tasks, and
+ // transitions with changes where
+ // 1. none are tasks, and
// 2. one is a starting-window recipient, or all change is behind starting window.
- if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
- && changeSize == 2
+ if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize)
+ && changeSize >= 1
// B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -918,9 +925,12 @@ public class Transitions implements RemoteCallable<Transitions>,
}
// An existing animation is playing, so see if we can merge.
final ActiveTransition playing = track.mActiveTransition;
+ final IBinder playingToken = playing.mToken;
+ final IBinder readyToken = ready.mToken;
+
if (ready.mAborted) {
// record as merged since it is no-op. Calls back into processReadyQueue
- onMerged(playing, ready);
+ onMerged(playingToken, readyToken);
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
@@ -928,14 +938,31 @@ public class Transitions implements RemoteCallable<Transitions>,
+ " in case they can be merged", ready, playing);
mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
- playing.mToken, (wct) -> onMerged(playing, ready));
+ playing.mToken, (wct) -> onMerged(playingToken, readyToken));
}
- private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ private void onMerged(@NonNull IBinder playingToken, @NonNull IBinder mergedToken) {
+ if (enforceShellThreadModel()) {
+ mMainExecutor.assertCurrentThread();
+ }
+
+ ActiveTransition playing = mKnownTransitions.get(playingToken);
+ if (playing == null) {
+ Log.e(TAG, "Merging into a non-existent transition: " + playingToken);
+ return;
+ }
+
+ ActiveTransition merged = mKnownTransitions.get(mergedToken);
+ if (merged == null) {
+ Log.e(TAG, "Merging a non-existent transition: " + mergedToken);
+ return;
+ }
+
if (playing.getTrack() != merged.getTrack()) {
throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
+ playing);
}
+
final Track track = mTracks.get(playing.getTrack());
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
merged, playing);
@@ -1033,20 +1060,6 @@ public class Transitions implements RemoteCallable<Transitions>,
return null;
}
- /** @deprecated */
- @java.lang.Deprecated
- public void setAfterMergeFinishTransaction(IBinder transition,
- SurfaceControl.Transaction afterMergeFinishT) {
- final ActiveTransition at = mKnownTransitions.get(transition);
- if (at == null) return;
- if (at.mAfterMergeFinishT != null) {
- Log.e(TAG, "Setting after-merge-t >1 time on transition: " + at.mInfo.getDebugId());
- at.mAfterMergeFinishT.merge(afterMergeFinishT);
- return;
- }
- at.mAfterMergeFinishT = afterMergeFinishT;
- }
-
/** Aborts a transition. This will still queue it up to maintain order. */
private void onAbort(ActiveTransition transition) {
final Track track = mTracks.get(transition.getTrack());
@@ -1079,13 +1092,17 @@ public class Transitions implements RemoteCallable<Transitions>,
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder token,
- @Nullable WindowContainerTransaction wct) {
+ private void onFinish(IBinder token, @Nullable WindowContainerTransaction wct) {
+ if (enforceShellThreadModel()) {
+ mMainExecutor.assertCurrentThread();
+ }
+
final ActiveTransition active = mKnownTransitions.get(token);
if (active == null) {
Log.e(TAG, "Trying to finish a non-existent transition: " + token);
return;
}
+
final Track track = mTracks.get(active.getTrack());
if (track == null || track.mActiveTransition != active) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -1107,7 +1124,6 @@ public class Transitions implements RemoteCallable<Transitions>,
}
// Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
- SurfaceControl.Transaction afterMergeFinish = active.mAfterMergeFinishT;
if (active.mMerged != null) {
for (int iM = 0; iM < active.mMerged.size(); ++iM) {
final ActiveTransition toMerge = active.mMerged.get(iM);
@@ -1127,21 +1143,6 @@ public class Transitions implements RemoteCallable<Transitions>,
fullFinish.merge(toMerge.mFinishT);
}
}
- if (toMerge.mAfterMergeFinishT != null) {
- if (afterMergeFinish == null) {
- afterMergeFinish = toMerge.mAfterMergeFinishT;
- } else {
- afterMergeFinish.merge(toMerge.mAfterMergeFinishT);
- }
- toMerge.mAfterMergeFinishT = null;
- }
- }
- }
- if (afterMergeFinish != null) {
- if (fullFinish == null) {
- fullFinish = afterMergeFinish;
- } else {
- fullFinish.merge(afterMergeFinish);
}
}
if (fullFinish != null) {
@@ -1231,7 +1232,7 @@ public class Transitions implements RemoteCallable<Transitions>,
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
- + "type=%d wct=%s handler=%s", type, wct, handler);
+ + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
final ActiveTransition active =
new ActiveTransition(mOrganizer.startNewTransition(type, wct));
active.mHandler = handler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
deleted file mode 100644
index c93e584aff9f..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util
-
-import android.util.Log
-import com.android.internal.protolog.common.IProtoLogGroup
-import com.android.internal.protolog.ProtoLog
-
-/**
- * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
- * logging from Kotlin classes as ProtoLog does not have support for Kotlin.
- *
- * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup].
- */
-// TODO(b/168581922): remove once ProtoLog adds support for Kotlin
-class KtProtoLog {
- companion object {
- /** @see [com.android.internal.protolog.common.ProtoLog.d] */
- fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.d(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.common.ProtoLog.v] */
- fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.v(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.common.ProtoLog.i] */
- fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.i(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.common.ProtoLog.w] */
- fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.w(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.common.ProtoLog.e] */
- fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.e(group.tag, String.format(messageString, *args))
- }
- }
-
- /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
- fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (group.isLogToLogcat) {
- Log.wtf(group.tag, String.format(messageString, *args))
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 95e0d79c212e..b9cb6d3d5007 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,12 +26,20 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ContentResolver;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.provider.Settings;
+import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.Display;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -45,39 +53,74 @@ import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+ private static final String TAG = "CaptionWindowDecorViewModel";
+
private final ShellTaskOrganizer mTaskOrganizer;
+ private final IWindowManager mWindowManager;
private final Context mContext;
private final Handler mMainHandler;
+ private final ShellExecutor mMainExecutor;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Transitions mTransitions;
+ private final Region mExclusionRegion = Region.obtain();
+ private final InputManager mInputManager;
private TaskOperations mTaskOperations;
+ /**
+ * Whether to pilfer the next motion event to send cancellations to the windows below.
+ * Useful when the caption window is spy and the gesture should be handled by the system
+ * instead of by the app for their custom header content.
+ */
+ private boolean mShouldPilferCaptionEvents;
+
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+ private final ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ mMainExecutor.execute(() -> {
+ mExclusionRegion.set(systemGestureExclusion);
+ });
+ }
+ };
+
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
+ ShellExecutor shellExecutor,
Choreographer mainChoreographer,
+ IWindowManager windowManager,
+ ShellInit shellInit,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions) {
mContext = context;
+ mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
+ mWindowManager = windowManager;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
@@ -87,6 +130,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
+ mInputManager = mContext.getSystemService(InputManager.class);
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ try {
+ mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
+ mContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register window manager callbacks", e);
+ }
}
@Override
@@ -178,8 +233,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
+ if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ decoration.setCaptionColor(Color.TRANSPARENT);
+ } else {
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
@@ -301,6 +360,49 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue.queue(wct);
}
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+
+ final int actionMasked = e.getActionMasked();
+ final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+ final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
+ || actionMasked == MotionEvent.ACTION_UP;
+ if (isDown) {
+ final boolean downInCustomizableCaptionRegion =
+ decoration.checkTouchEventInCustomizableRegion(e);
+ final boolean downInExclusionRegion = mExclusionRegion.contains(
+ (int) e.getRawX(), (int) e.getRawY());
+ final boolean isTransparentCaption =
+ TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
+ // MotionEvent's coordinates are relative to view, we want location in window
+ // to offset position relative to caption as a whole.
+ int[] viewLocation = new int[2];
+ v.getLocationInWindow(viewLocation);
+ final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e,
+ new Point(viewLocation[0], viewLocation[1]));
+ // The caption window may be a spy window when the caption background is
+ // transparent, which means events will fall through to the app window. Make
+ // sure to cancel these events if they do not happen in the intersection of the
+ // customizable region and what the app reported as exclusion areas, because
+ // the drag-move or other caption gestures should take priority outside those
+ // regions.
+ mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
+ }
+
+ if (!mShouldPilferCaptionEvents) {
+ // The event will be handled by a window below or pilfered by resize handler.
+ return false;
+ }
+ // Otherwise pilfer so that windows below receive cancellations for this gesture, and
+ // continue normal handling as a caption gesture.
+ if (mInputManager != null) {
+ // TODO(b/352127475): Only pilfer once per gesture
+ mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ }
+ if (isUpOrCancel) {
+ // Gesture is finished, reset state.
+ mShouldPilferCaptionEvents = false;
+ }
return mDragDetector.onMotionEvent(e);
}
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 d0ca5b0fdce6..7e1b973a98f4 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
@@ -21,6 +21,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -28,22 +29,27 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.util.Size;
import android.view.Choreographer;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowManager;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -177,12 +183,44 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
shouldSetTaskPositionAndCrop);
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw,
+ boolean setTaskCropAndPosition) {
+ relayoutParams.reset();
+ relayoutParams.mRunningTaskInfo = taskInfo;
+ relayoutParams.mLayoutResId = R.layout.caption_window_decor;
+ relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+ relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+
+ if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ // If the app is requesting to customize the caption bar, allow input to fall
+ // through to the windows below so that the app can respond to input events on
+ // their custom content.
+ relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
+ final RelayoutParams.OccludingCaptionElement backButtonElement =
+ new RelayoutParams.OccludingCaptionElement();
+ backButtonElement.mWidthResId = R.dimen.caption_left_buttons_width;
+ backButtonElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
+ relayoutParams.mOccludingCaptionElements.add(backButtonElement);
+ // Then, the right-aligned section (minimize, maximize and close buttons).
+ final RelayoutParams.OccludingCaptionElement controlsElement =
+ new RelayoutParams.OccludingCaptionElement();
+ controlsElement.mWidthResId = R.dimen.caption_right_buttons_width;
+ controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
+ relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ }
+
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
- final int shadowRadiusID = taskInfo.isFocused
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
@@ -191,13 +229,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mRelayoutParams.reset();
- mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
- mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+ updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
+ setTaskCropAndPosition);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -303,6 +336,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragResizeListener = null;
}
+ /**
+ * Checks whether the touch event falls inside the customizable caption region.
+ */
+ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
+ return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
+ }
+
+ boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+ return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
+ }
+
@Override
public void close() {
closeDragResizeListener();
@@ -311,6 +355,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return getCaptionHeightIdStatic(windowingMode);
+ }
+
+ private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return R.dimen.freeform_decor_caption_height;
}
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 a05dbf844db0..d12b9060bee3 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
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -32,7 +33,7 @@ import static android.view.WindowInsets.Type.statusBars;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
+import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -41,12 +42,17 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -73,6 +79,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
@@ -81,7 +88,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -91,7 +97,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -105,7 +111,6 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import java.io.PrintWriter;
-import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
@@ -131,6 +136,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private final DesktopTasksController mDesktopTasksController;
private final InputManager mInputManager;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -186,7 +192,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ InteractionJankMonitor interactionJankMonitor
) {
this(
context,
@@ -207,7 +214,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
rootTaskDisplayAreaOrganizer,
- new SparseArray<>());
+ new SparseArray<>(),
+ interactionJankMonitor);
}
@VisibleForTesting
@@ -230,7 +238,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
+ InteractionJankMonitor interactionJankMonitor) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -253,6 +262,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId = windowDecorByTaskId;
mSysUIPackageName = mContext.getResources().getString(
com.android.internal.R.string.config_systemUi);
+ mInteractionJankMonitor = interactionJankMonitor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -388,8 +398,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
return;
}
- InteractionJankMonitorUtils.beginTracing(
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, decoration.mTaskSurface, tag);
+ mInteractionJankMonitor.begin(
+ decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -406,6 +416,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.closeMaximizeMenu();
}
+ private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ openInBrowser(uri);
+ decor.closeHandleMenu();
+ decor.closeMaximizeMenu();
+ }
+
+ private void openInBrowser(Uri uri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .setComponent(getDefaultBrowser())
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private ComponentName getDefaultBrowser() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+ final ResolveInfo info = mContext.getPackageManager()
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return info.getComponentInfo().getComponentName();
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -485,6 +515,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
+ } else if (id == R.id.open_in_browser_button) {
+ // TODO(b/346441962): let the decoration handle the click gesture and only call back
+ // to the ViewModel via #setOpenInBrowserClickListener
+ decoration.onOpenInBrowserClick();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
@@ -1006,7 +1040,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private void createInputChannel(int displayId) {
final InputManager inputManager = mContext.getSystemService(InputManager.class);
final InputMonitor inputMonitor =
- mInputMonitorFactory.create(inputManager, mContext);
+ mInputMonitorFactory.create(inputManager, displayId);
final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
@@ -1029,12 +1063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& taskInfo.isFocused) {
return false;
}
- // TODO(b/347289970): Consider replacing with API
if (Flags.enableDesktopWindowingModalsPolicy()
- && isSingleTopActivityTranslucent(taskInfo)) {
- return false;
- }
- if (isSystemUIApplication(taskInfo)) {
+ && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
return DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1077,7 +1107,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions);
+ mDragStartListener, mTransitions, mInteractionJankMonitor);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
@@ -1091,6 +1121,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
onSnapResize(taskId, false /* isLeft */);
});
+ windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
@@ -1113,14 +1144,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
&& mSplitScreenController.isTaskInSplitScreen(taskId);
}
- // TODO(b/347289970): Consider replacing with API
- private boolean isSystemUIApplication(RunningTaskInfo taskInfo) {
- if (taskInfo.baseActivity != null) {
- return (Objects.equals(taskInfo.baseActivity.getPackageName(), mSysUIPackageName));
- }
- return false;
- }
-
private void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + "DesktopModeWindowDecorViewModel");
@@ -1171,8 +1194,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
static class InputMonitorFactory {
- InputMonitor create(InputManager inputManager, Context context) {
- return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ InputMonitor create(InputManager inputManager, int displayId) {
+ return inputManager.monitorGestureInput("caption-touch", displayId);
}
}
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 f53c21d352b3..5d662b20ebb9 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
@@ -31,6 +31,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResiz
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -43,6 +44,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
import android.util.Log;
@@ -67,7 +69,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
@@ -87,6 +89,7 @@ import java.util.function.Supplier;
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+ private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
@VisibleForTesting
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -123,6 +126,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Bitmap mResizeVeilBitmap;
private CharSequence mAppName;
+ private CapturedLink mCapturedLink;
+ private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -137,6 +142,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// being hovered. There's a small delay after stopping the hover, to allow a quick reentry
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
+ private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
DesktopModeWindowDecoration(
Context context,
@@ -152,8 +158,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE);
+ new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -231,6 +236,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
+ void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ mOpenInBrowserClickListener = listener;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -322,6 +331,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
+
+ if (Flags.enableDesktopWindowingAppToWeb()) {
+ setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
+ }
+
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
@@ -366,6 +380,28 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
}
+ private void setCapturedLink(Uri capturedLink, long timeStamp) {
+ if (capturedLink == null
+ || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
+ return;
+ }
+ mCapturedLink = new CapturedLink(capturedLink, timeStamp);
+ mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
+ }
+
+ private void onCapturedLinkExpired() {
+ mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
+ if (mCapturedLink != null) {
+ mCapturedLink.setExpired();
+ }
+ }
+
+ void onOpenInBrowserClick() {
+ if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
+ mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ onCapturedLinkExpired();
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -600,12 +636,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (mAppIconBitmap != null && mAppName != null) {
return;
}
- final ActivityInfo activityInfo = mTaskInfo.topActivityInfo;
- if (activityInfo == null) {
- Log.e(TAG, "Top activity info not found in task");
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ if (baseActivity == null) {
+ Log.e(TAG, "Base activity component not found in task");
return;
}
- PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -619,6 +656,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Base activity's component name cannot be found on the system");
} finally {
Trace.endSection();
}
@@ -813,21 +852,28 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void createHandleMenu(SplitScreenController splitScreenController) {
loadAppInfoIfNeeded();
- mHandleMenu = new HandleMenu.Builder(this)
- .setAppIcon(mAppIconBitmap)
- .setAppName(mAppName)
- .setOnClickListener(mOnCaptionButtonClickListener)
- .setOnTouchListener(mOnCaptionTouchListener)
- .setLayoutId(mRelayoutParams.mLayoutResId)
- .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
- .setCaptionHeight(mResult.mCaptionHeight)
- .setDisplayController(mDisplayController)
- .setSplitScreenController(splitScreenController)
- .build();
+ mHandleMenu = new HandleMenu(
+ this,
+ mRelayoutParams.mLayoutResId,
+ mOnCaptionButtonClickListener,
+ mOnCaptionTouchListener,
+ mAppIconBitmap,
+ mAppName,
+ mDisplayController,
+ splitScreenController,
+ DesktopModeStatus.canEnterDesktopMode(mContext),
+ browserLinkAvailable(),
+ mResult.mCaptionHeight
+ );
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
+ @VisibleForTesting
+ boolean browserLinkAvailable() {
+ return mCapturedLink != null && !mCapturedLink.mExpired;
+ }
+
/**
* Close the handle menu window.
*/
@@ -1117,6 +1163,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @VisibleForTesting
+ static class CapturedLink {
+ private final long mTimeStamp;
+ private final Uri mUri;
+ private boolean mExpired;
+
+ CapturedLink(@NonNull Uri uri, long timeStamp) {
+ mUri = uri;
+ mTimeStamp = timeStamp;
+ mExpired = false;
+ }
+
+ void setExpired() {
+ mExpired = true;
+ }
+ }
+
+
+ /** Listener for the handle menu's "Open in browser" button */
+ interface OpenInBrowserClickListener {
+
+ /** Inform the implementing class that the "Open in browser" button has been clicked */
+ void onClick(DesktopModeWindowDecoration decoration, Uri uri);
+ }
+
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index d48ce536f2b3..2fd3eaa37ec3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -33,7 +33,7 @@ import androidx.annotation.NonNull;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index a3616f65f3b2..32df8b3b2c7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -468,8 +468,7 @@ class DragResizeInputListener implements AutoCloseable {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getDisplayId(), e.getDeviceId(),
- e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
- e.getYCursorPosition());
+ e.getPointerId(/*pointerIndex=*/0), e.getX(), e.getY());
result = true;
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index b5d1d4a76342..ba5f0791a010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,9 +33,6 @@ import android.graphics.Region;
import android.util.Size;
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
import com.android.wm.shell.R;
import java.util.Objects;
@@ -44,11 +41,6 @@ import java.util.Objects;
* Geometry for a drag resize region for a particular window.
*/
final class DragResizeWindowGeometry {
- // TODO(b/337264971) clean up when no longer needed
- @VisibleForTesting static final boolean DEBUG = true;
- // The additional width to apply to edge resize bounds just for logging when a touch is
- // close.
- @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
private final int mTaskCornerRadius;
private final Size mTaskSize;
// The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -60,8 +52,6 @@ final class DragResizeWindowGeometry {
private final @NonNull TaskCorners mFineTaskCorners;
// The bounds for each edge drag region, which can resize the task in one direction.
private final @NonNull TaskEdges mTaskEdges;
- // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
- private final @Nullable TaskEdges mDebugTaskEdges;
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -74,11 +64,6 @@ final class DragResizeWindowGeometry {
// Save touch areas for each edge.
mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
- if (DEBUG) {
- mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
- } else {
- mDebugTaskEdges = null;
- }
}
/**
@@ -120,13 +105,7 @@ final class DragResizeWindowGeometry {
*/
void union(@NonNull Region region) {
// Apply the edge resize regions.
- if (inDebugMode()) {
- // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
- // touch due to the size of the edge region.
- mDebugTaskEdges.union(region);
- } else {
- mTaskEdges.union(region);
- }
+ mTaskEdges.union(region);
if (enableWindowingEdgeDragResize()) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -219,10 +198,6 @@ final class DragResizeWindowGeometry {
@DragPositioningCallback.CtrlType
private int calculateEdgeResizeCtrlType(float x, float y) {
- if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
- && !mTaskEdges.contains((int) x, (int) y))) {
- return CTRL_TYPE_UNDEFINED;
- }
int ctrlType = CTRL_TYPE_UNDEFINED;
// mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
// sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -313,9 +288,7 @@ final class DragResizeWindowGeometry {
&& this.mResizeHandleThickness == other.mResizeHandleThickness
&& this.mFineTaskCorners.equals(other.mFineTaskCorners)
&& this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
- && (inDebugMode()
- ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
- : this.mTaskEdges.equals(other.mTaskEdges));
+ && this.mTaskEdges.equals(other.mTaskEdges);
}
@Override
@@ -326,11 +299,7 @@ final class DragResizeWindowGeometry {
mResizeHandleThickness,
mFineTaskCorners,
mLargeTaskCorners,
- (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
- }
-
- private boolean inDebugMode() {
- return DEBUG && mDebugTaskEdges != null;
+ mTaskEdges);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
deleted file mode 100644
index df0836c1121d..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.windowdecor;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.window.SurfaceSyncGroup;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.window.flags.Flags;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
-
-/**
- * Handle menu opened when the appropriate button is clicked on.
- *
- * Displays up to 3 pills that show the following:
- * App Info: App name, app icon, and collapse button to close the menu.
- * Windowing Options(Proto 2 only): Buttons to change windowing modes.
- * Additional Options: Miscellaneous functions including screenshot and closing task.
- */
-class HandleMenu {
- private static final String TAG = "HandleMenu";
- private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
- private final Context mContext;
- private final DesktopModeWindowDecoration mParentDecor;
- @VisibleForTesting
- AdditionalViewContainer mHandleMenuViewContainer;
- // Position of the handle menu used for laying out the handle view.
- @VisibleForTesting
- final PointF mHandleMenuPosition = new PointF();
- // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
- // may be in a different coordinate space than the input coordinates. Therefore, we still care
- // about the menu's coordinates relative to the display as a whole, so we need to maintain
- // those as well.
- final Point mGlobalMenuPosition = new Point();
- private final boolean mShouldShowWindowingPill;
- private final Bitmap mAppIconBitmap;
- private final CharSequence mAppName;
- private final View.OnClickListener mOnClickListener;
- private final View.OnTouchListener mOnTouchListener;
- private final RunningTaskInfo mTaskInfo;
- private final DisplayController mDisplayController;
- private final SplitScreenController mSplitScreenController;
- private final int mLayoutResId;
- private int mMarginMenuTop;
- private int mMarginMenuStart;
- private int mMenuHeight;
- private int mMenuWidth;
- private final int mCaptionHeight;
- private HandleMenuAnimator mHandleMenuAnimator;
-
-
- HandleMenu(DesktopModeWindowDecoration parentDecor, int layoutResId,
- View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Bitmap appIcon, CharSequence appName, DisplayController displayController,
- SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
- int captionHeight) {
- mParentDecor = parentDecor;
- mContext = mParentDecor.mDecorWindowContext;
- mTaskInfo = mParentDecor.mTaskInfo;
- mDisplayController = displayController;
- mSplitScreenController = splitScreenController;
- mLayoutResId = layoutResId;
- mOnClickListener = onClickListener;
- mOnTouchListener = onTouchListener;
- mAppIconBitmap = appIcon;
- mAppName = appName;
- mShouldShowWindowingPill = shouldShowWindowingPill;
- mCaptionHeight = captionHeight;
- loadHandleMenuDimensions();
- updateHandleMenuPillPositions();
- }
-
- void show() {
- final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-
- createHandleMenuViewContainer(t, ssg);
- ssg.addTransaction(t);
- ssg.markSyncReady();
- setupHandleMenu();
- animateHandleMenu();
- }
-
- private void createHandleMenuViewContainer(SurfaceControl.Transaction t,
- SurfaceSyncGroup ssg) {
- final int x = (int) mHandleMenuPosition.x;
- final int y = (int) mHandleMenuPosition.y;
- if (!mTaskInfo.isFreeform() && Flags.enableAdditionalWindowsAboveStatusBar()) {
- mHandleMenuViewContainer = new AdditionalSystemViewContainer(mContext,
- R.layout.desktop_mode_window_decor_handle_menu, mTaskInfo.taskId,
- x, y, mMenuWidth, mMenuHeight);
- } else {
- mHandleMenuViewContainer = mParentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, mMenuWidth, mMenuHeight);
- }
- final View handleMenuView = mHandleMenuViewContainer.getView();
- mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
- }
-
- /**
- * Animates the appearance of the handle menu and its three pills.
- */
- private void animateHandleMenu() {
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
- } else {
- mHandleMenuAnimator.animateOpen();
- }
- }
-
- /**
- * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
- * pill.
- */
- private void setupHandleMenu() {
- final View handleMenu = mHandleMenuViewContainer.getView();
- handleMenu.setOnTouchListener(mOnTouchListener);
- setupAppInfoPill(handleMenu);
- if (mShouldShowWindowingPill) {
- setupWindowingPill(handleMenu);
- }
- setupMoreActionsPill(handleMenu);
- }
-
- /**
- * Set up interactive elements of handle menu's app info pill.
- */
- private void setupAppInfoPill(View handleMenu) {
- final HandleMenuImageButton collapseBtn =
- handleMenu.findViewById(R.id.collapse_menu_button);
- final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
- final TextView appName = handleMenu.findViewById(R.id.application_name);
- collapseBtn.setOnClickListener(mOnClickListener);
- collapseBtn.setTaskInfo(mTaskInfo);
- appIcon.setImageBitmap(mAppIconBitmap);
- appName.setText(mAppName);
- }
-
- /**
- * Set up interactive elements and color of handle menu's windowing pill.
- */
- private void setupWindowingPill(View handleMenu) {
- final ImageButton fullscreenBtn = handleMenu.findViewById(
- R.id.fullscreen_button);
- final ImageButton splitscreenBtn = handleMenu.findViewById(
- R.id.split_screen_button);
- final ImageButton floatingBtn = handleMenu.findViewById(R.id.floating_button);
- // TODO: Remove once implemented.
- floatingBtn.setVisibility(View.GONE);
-
- final ImageButton desktopBtn = handleMenu.findViewById(R.id.desktop_button);
- fullscreenBtn.setOnClickListener(mOnClickListener);
- splitscreenBtn.setOnClickListener(mOnClickListener);
- floatingBtn.setOnClickListener(mOnClickListener);
- desktopBtn.setOnClickListener(mOnClickListener);
- // The button corresponding to the windowing mode that the task is currently in uses a
- // different color than the others.
- final ColorStateList[] iconColors = getWindowingIconColor();
- final ColorStateList inActiveColorStateList = iconColors[0];
- final ColorStateList activeColorStateList = iconColors[1];
- final int windowingMode = mTaskInfo.getWindowingMode();
- fullscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_FULLSCREEN
- ? activeColorStateList : inActiveColorStateList);
- splitscreenBtn.setImageTintList(windowingMode == WINDOWING_MODE_MULTI_WINDOW
- ? activeColorStateList : inActiveColorStateList);
- floatingBtn.setImageTintList(windowingMode == WINDOWING_MODE_PINNED
- ? activeColorStateList : inActiveColorStateList);
- desktopBtn.setImageTintList(windowingMode == WINDOWING_MODE_FREEFORM
- ? activeColorStateList : inActiveColorStateList);
- }
-
- /**
- * Set up interactive elements & height of handle menu's more actions pill
- */
- private void setupMoreActionsPill(View handleMenu) {
- if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
- handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
- }
- }
-
- /**
- * Returns array of windowing icon color based on current UI theme. First element of the
- * array is for inactive icons and the second is for active icons.
- */
- private ColorStateList[] getWindowingIconColor() {
- final int mode = mContext.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK;
- final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
- final TypedArray typedArray = mContext.obtainStyledAttributes(new int[]{
- com.android.internal.R.attr.materialColorOnSurface,
- com.android.internal.R.attr.materialColorPrimary});
- final int inActiveColor = typedArray.getColor(0, isNightMode ? Color.WHITE : Color.BLACK);
- final int activeColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
- typedArray.recycle();
- return new ColorStateList[]{ColorStateList.valueOf(inActiveColor),
- ColorStateList.valueOf(activeColor)};
- }
-
- /**
- * Updates handle menu's position variables to reflect its next position.
- */
- private void updateHandleMenuPillPositions() {
- int menuX;
- final int menuY;
- final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
- updateGlobalMenuPosition(taskBounds);
- if (mLayoutResId == R.layout.desktop_mode_app_header) {
- // Align the handle menu to the left side of the caption.
- menuX = mMarginMenuStart;
- menuY = mMarginMenuTop;
- } else {
- if (Flags.enableAdditionalWindowsAboveStatusBar()) {
- // In a focused decor, we use global coordinates for handle menu. Therefore we
- // need to account for other factors like split stage and menu/handle width to
- // center the menu.
- final DisplayLayout layout = mDisplayController
- .getDisplayLayout(mTaskInfo.displayId);
- menuX = mGlobalMenuPosition.x + ((mMenuWidth - layout.width()) / 2);
- menuY = mGlobalMenuPosition.y + ((mMenuHeight - layout.height()) / 2);
- } else {
- menuX = (taskBounds.width() / 2) - (mMenuWidth / 2);
- menuY = mMarginMenuTop;
- }
- }
- // Handle Menu position setup.
- mHandleMenuPosition.set(menuX, menuY);
- }
-
- private void updateGlobalMenuPosition(Rect taskBounds) {
- if (mTaskInfo.isFreeform()) {
- mGlobalMenuPosition.set(taskBounds.left + mMarginMenuStart,
- taskBounds.top + mMarginMenuTop);
- } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- mGlobalMenuPosition.set(
- (taskBounds.width() / 2) - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop
- );
- } else if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- final int splitPosition = mSplitScreenController.getSplitPosition(mTaskInfo.taskId);
- final Rect leftOrTopStageBounds = new Rect();
- final Rect rightOrBottomStageBounds = new Rect();
- mSplitScreenController.getStageBounds(leftOrTopStageBounds,
- rightOrBottomStageBounds);
- // TODO(b/343561161): This needs to be calculated differently if the task is in
- // top/bottom split.
- if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- mGlobalMenuPosition.set(leftOrTopStageBounds.width()
- + (rightOrBottomStageBounds.width() / 2)
- - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop);
- } else if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
- mGlobalMenuPosition.set((leftOrTopStageBounds.width() / 2)
- - (mMenuWidth / 2) + mMarginMenuStart,
- mMarginMenuTop);
- }
- }
- }
-
- /**
- * Update pill layout, in case task changes have caused positioning to change.
- */
- void relayout(SurfaceControl.Transaction t) {
- if (mHandleMenuViewContainer != null) {
- updateHandleMenuPillPositions();
- mHandleMenuViewContainer.setPosition(t, mHandleMenuPosition.x, mHandleMenuPosition.y);
- }
- }
-
- /**
- * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
- * Note this should only be called when a regular onClick/onHover is not possible
- * (i.e. the button was clicked through status bar layer)
- *
- * @param ev the MotionEvent to compare against.
- */
- void checkMotionEvent(MotionEvent ev) {
- // If the menu view is above status bar, we can let the views handle input directly.
- if (isViewAboveStatusBar()) return;
- final View handleMenu = mHandleMenuViewContainer.getView();
- final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
- final PointF inputPoint = translateInputToLocalSpace(ev);
- final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y);
- final int action = ev.getActionMasked();
- collapse.setHovered(inputInCollapseButton && action != ACTION_UP);
- collapse.setPressed(inputInCollapseButton && action == ACTION_DOWN);
- if (action == ACTION_UP && inputInCollapseButton) {
- collapse.performClick();
- }
- }
-
- private boolean isViewAboveStatusBar() {
- return Flags.enableAdditionalWindowsAboveStatusBar()
- && !mTaskInfo.isFreeform();
- }
-
- // Translate the input point from display coordinates to the same space as the handle menu.
- private PointF translateInputToLocalSpace(MotionEvent ev) {
- return new PointF(ev.getX() - mHandleMenuPosition.x,
- ev.getY() - mHandleMenuPosition.y);
- }
-
- /**
- * A valid menu input is one of the following:
- * An input that happens in the menu views.
- * Any input before the views have been laid out.
- *
- * @param inputPoint the input to compare against.
- */
- boolean isValidMenuInput(PointF inputPoint) {
- if (!viewsLaidOut()) return true;
- if (!isViewAboveStatusBar()) {
- return pointInView(
- mHandleMenuViewContainer.getView(),
- inputPoint.x - mHandleMenuPosition.x,
- inputPoint.y - mHandleMenuPosition.y);
- } else {
- // Handle menu exists in a different coordinate space when added to WindowManager.
- // Therefore we must compare the provided input coordinates to global menu coordinates.
- // This includes factoring for split stage as input coordinates are relative to split
- // stage position, not relative to the display as a whole.
- PointF inputRelativeToMenu = new PointF(
- inputPoint.x - mGlobalMenuPosition.x,
- inputPoint.y - mGlobalMenuPosition.y
- );
- if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
- == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- // TODO(b/343561161): This also needs to be calculated differently if
- // the task is in top/bottom split.
- Rect leftStageBounds = new Rect();
- mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
- inputRelativeToMenu.x += leftStageBounds.width();
- }
- return pointInView(
- mHandleMenuViewContainer.getView(),
- inputRelativeToMenu.x,
- inputRelativeToMenu.y);
- }
- }
-
- private boolean pointInView(View v, float x, float y) {
- return v != null && v.getLeft() <= x && v.getRight() >= x
- && v.getTop() <= y && v.getBottom() >= y;
- }
-
- /**
- * Check if the views for handle menu can be seen.
- */
- private boolean viewsLaidOut() {
- return mHandleMenuViewContainer.getView().isLaidOut();
- }
-
- private void loadHandleMenuDimensions() {
- final Resources resources = mContext.getResources();
- mMenuWidth = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_width);
- mMenuHeight = getHandleMenuHeight(resources);
- mMarginMenuTop = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_top);
- mMarginMenuStart = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_margin_start);
- }
-
- /**
- * Determines handle menu height based on if windowing pill should be shown.
- */
- private int getHandleMenuHeight(Resources resources) {
- int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
- if (!mShouldShowWindowingPill) {
- menuHeight -= loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height);
- }
- if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
- menuHeight -= loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
- }
- return menuHeight;
- }
-
- private int loadDimensionPixelSize(Resources resources, int resourceId) {
- if (resourceId == Resources.ID_NULL) {
- return 0;
- }
- return resources.getDimensionPixelSize(resourceId);
- }
-
- void close() {
- final Runnable after = () -> {
- mHandleMenuViewContainer.releaseView();
- mHandleMenuViewContainer = null;
- };
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
- mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
- } else {
- mHandleMenuAnimator.animateClose(after);
- }
- }
-
- static final class Builder {
- private final DesktopModeWindowDecoration mParent;
- private CharSequence mName;
- private Bitmap mAppIcon;
- private View.OnClickListener mOnClickListener;
- private View.OnTouchListener mOnTouchListener;
- private int mLayoutId;
- private boolean mShowWindowingPill;
- private int mCaptionHeight;
- private DisplayController mDisplayController;
- private SplitScreenController mSplitScreenController;
-
- Builder(@NonNull DesktopModeWindowDecoration parent) {
- mParent = parent;
- }
-
- Builder setAppName(@Nullable CharSequence name) {
- mName = name;
- return this;
- }
-
- Builder setAppIcon(@Nullable Bitmap appIcon) {
- mAppIcon = appIcon;
- return this;
- }
-
- Builder setOnClickListener(@Nullable View.OnClickListener onClickListener) {
- mOnClickListener = onClickListener;
- return this;
- }
-
- Builder setOnTouchListener(@Nullable View.OnTouchListener onTouchListener) {
- mOnTouchListener = onTouchListener;
- return this;
- }
-
- Builder setLayoutId(int layoutId) {
- mLayoutId = layoutId;
- return this;
- }
-
- Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
- mShowWindowingPill = windowingButtonsVisible;
- return this;
- }
-
- Builder setCaptionHeight(int captionHeight) {
- mCaptionHeight = captionHeight;
- return this;
- }
-
- Builder setDisplayController(DisplayController displayController) {
- mDisplayController = displayController;
- return this;
- }
-
- Builder setSplitScreenController(SplitScreenController splitScreenController) {
- mSplitScreenController = splitScreenController;
- return this;
- }
-
- HandleMenu build() {
- return new HandleMenu(mParent, mLayoutId, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
- mShowWindowingPill, mCaptionHeight);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
new file mode 100644
index 000000000000..bce233fb0b52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.annotation.DimenRes
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+import android.view.View
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import android.window.SurfaceSyncGroup
+import androidx.annotation.VisibleForTesting
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.split.SplitScreenConstants
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
+import com.android.wm.shell.windowdecor.extension.isFullscreen
+
+/**
+ * Handle menu opened when the appropriate button is clicked on.
+ *
+ * Displays up to 3 pills that show the following:
+ * App Info: App name, app icon, and collapse button to close the menu.
+ * Windowing Options(Proto 2 only): Buttons to change windowing modes.
+ * Additional Options: Miscellaneous functions including screenshot and closing task.
+ */
+class HandleMenu(
+ private val parentDecor: DesktopModeWindowDecoration,
+ private val layoutResId: Int,
+ private val onClickListener: View.OnClickListener?,
+ private val onTouchListener: View.OnTouchListener?,
+ private val appIconBitmap: Bitmap?,
+ private val appName: CharSequence?,
+ private val displayController: DisplayController,
+ private val splitScreenController: SplitScreenController,
+ private val shouldShowWindowingPill: Boolean,
+ private val shouldShowBrowserPill: Boolean,
+ private val captionHeight: Int
+) {
+ private val context: Context = parentDecor.mDecorWindowContext
+ private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
+
+ private val isViewAboveStatusBar: Boolean
+ get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
+
+ private val pillElevation: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_elevation)
+ private val pillTopMargin: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ private val menuWidth = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_width) + pillElevation
+ private val menuHeight = getHandleMenuHeight()
+ private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
+ private val marginMenuStart = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_margin_start)
+
+ private var handleMenuAnimator: HandleMenuAnimator? = null
+
+ @VisibleForTesting
+ var handleMenuViewContainer: AdditionalViewContainer? = null
+
+ // Position of the handle menu used for laying out the handle view.
+ @VisibleForTesting
+ val handleMenuPosition: PointF = PointF()
+
+ // With the introduction of {@link AdditionalSystemViewContainer}, {@link mHandleMenuPosition}
+ // may be in a different coordinate space than the input coordinates. Therefore, we still care
+ // about the menu's coordinates relative to the display as a whole, so we need to maintain
+ // those as well.
+ private val globalMenuPosition: Point = Point()
+
+ /**
+ * An a array of windowing icon color based on current UI theme. First element of the
+ * array is for inactive icons and the second is for active icons.
+ */
+ private val windowingIconColor: Array<ColorStateList>
+ get() {
+ val mode = (context.resources.configuration.uiMode
+ and Configuration.UI_MODE_NIGHT_MASK)
+ val isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES)
+ val typedArray = context.obtainStyledAttributes(
+ intArrayOf(
+ com.android.internal.R.attr.materialColorOnSurface,
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ )
+ val inActiveColor =
+ typedArray.getColor(0, if (isNightMode) Color.WHITE else Color.BLACK)
+ val activeColor = typedArray.getColor(1, if (isNightMode) Color.WHITE else Color.BLACK)
+ typedArray.recycle()
+ return arrayOf(
+ ColorStateList.valueOf(inActiveColor),
+ ColorStateList.valueOf(activeColor)
+ )
+ }
+
+ init {
+ updateHandleMenuPillPositions()
+ }
+
+ fun show() {
+ val ssg = SurfaceSyncGroup(TAG)
+ val t = SurfaceControl.Transaction()
+
+ createHandleMenuViewContainer(t, ssg)
+ ssg.addTransaction(t)
+ ssg.markSyncReady()
+ setupHandleMenu()
+ animateHandleMenu()
+ }
+
+ private fun createHandleMenuViewContainer(
+ t: SurfaceControl.Transaction,
+ ssg: SurfaceSyncGroup
+ ) {
+ val x = handleMenuPosition.x.toInt()
+ val y = handleMenuPosition.y.toInt()
+ handleMenuViewContainer =
+ if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
+ AdditionalSystemViewContainer(
+ context = context,
+ layoutId = R.layout.desktop_mode_window_decor_handle_menu,
+ taskId = taskInfo.taskId,
+ x = x,
+ y = y,
+ width = menuWidth,
+ height = menuHeight
+ )
+ } else {
+ parentDecor.addWindow(
+ R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
+ t, ssg, x, y, menuWidth, menuHeight
+ )
+ }
+ handleMenuViewContainer?.view?.let { view ->
+ handleMenuAnimator =
+ HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
+ }
+ }
+
+ /**
+ * Animates the appearance of the handle menu and its three pills.
+ */
+ private fun animateHandleMenu() {
+ when (taskInfo.windowingMode) {
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ handleMenuAnimator?.animateCaptionHandleExpandToOpen()
+ }
+ else -> {
+ handleMenuAnimator?.animateOpen()
+ }
+ }
+ }
+
+ /**
+ * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
+ * pill.
+ */
+ private fun setupHandleMenu() {
+ val handleMenu = handleMenuViewContainer?.view ?: return
+ handleMenu.setOnTouchListener(onTouchListener)
+ setupAppInfoPill(handleMenu)
+ if (shouldShowWindowingPill) {
+ setupWindowingPill(handleMenu)
+ }
+ setupMoreActionsPill(handleMenu)
+ setupOpenInBrowserPill(handleMenu)
+ }
+
+ /**
+ * Set up interactive elements of handle menu's app info pill.
+ */
+ private fun setupAppInfoPill(handleMenu: View) {
+ val collapseBtn = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+ val appIcon = handleMenu.findViewById<ImageView>(R.id.application_icon)
+ val appName = handleMenu.findViewById<TextView>(R.id.application_name)
+ collapseBtn.setOnClickListener(onClickListener)
+ collapseBtn.taskInfo = taskInfo
+ appIcon.setImageBitmap(appIconBitmap)
+ appName.text = this.appName
+ }
+
+ /**
+ * Set up interactive elements and color of handle menu's windowing pill.
+ */
+ private fun setupWindowingPill(handleMenu: View) {
+ val fullscreenBtn = handleMenu.findViewById<ImageButton>(R.id.fullscreen_button)
+ val splitscreenBtn = handleMenu.findViewById<ImageButton>(R.id.split_screen_button)
+ val floatingBtn = handleMenu.findViewById<ImageButton>(R.id.floating_button)
+ // TODO: Remove once implemented.
+ floatingBtn.visibility = View.GONE
+
+ val desktopBtn = handleMenu.findViewById<ImageButton>(R.id.desktop_button)
+ fullscreenBtn.setOnClickListener(onClickListener)
+ splitscreenBtn.setOnClickListener(onClickListener)
+ floatingBtn.setOnClickListener(onClickListener)
+ desktopBtn.setOnClickListener(onClickListener)
+ // The button corresponding to the windowing mode that the task is currently in uses a
+ // different color than the others.
+ val iconColors = windowingIconColor
+ val inActiveColorStateList = iconColors[0]
+ val activeColorStateList = iconColors[1]
+ fullscreenBtn.imageTintList = if (taskInfo.isFullscreen) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ splitscreenBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ floatingBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_PINNED) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ desktopBtn.imageTintList = if (taskInfo.isFreeform) {
+ activeColorStateList
+ } else {
+ inActiveColorStateList
+ }
+ }
+
+ /**
+ * Set up interactive elements & height of handle menu's more actions pill
+ */
+ private fun setupMoreActionsPill(handleMenu: View) {
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ handleMenu.findViewById<View>(R.id.more_actions_pill).visibility = View.GONE
+ }
+ }
+
+ private fun setupOpenInBrowserPill(handleMenu: View) {
+ if (!shouldShowBrowserPill) {
+ handleMenu.findViewById<View>(R.id.open_in_browser_pill).visibility = View.GONE
+ return
+ }
+ val browserButton = handleMenu.findViewById<Button>(R.id.open_in_browser_button)
+ browserButton.setOnClickListener(onClickListener)
+ }
+
+ /**
+ * Updates handle menu's position variables to reflect its next position.
+ */
+ private fun updateHandleMenuPillPositions() {
+ val menuX: Int
+ val menuY: Int
+ val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
+ updateGlobalMenuPosition(taskBounds)
+ if (layoutResId == R.layout.desktop_mode_app_header) {
+ // Align the handle menu to the left side of the caption.
+ menuX = marginMenuStart
+ menuY = marginMenuTop
+ } else {
+ if (Flags.enableAdditionalWindowsAboveStatusBar()) {
+ // In a focused decor, we use global coordinates for handle menu. Therefore we
+ // need to account for other factors like split stage and menu/handle width to
+ // center the menu.
+ menuX = globalMenuPosition.x
+ menuY = globalMenuPosition.y
+ } else {
+ menuX = (taskBounds.width() / 2) - (menuWidth / 2)
+ menuY = marginMenuTop
+ }
+ }
+ // Handle Menu position setup.
+ handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
+ }
+
+ private fun updateGlobalMenuPosition(taskBounds: Rect) {
+ when (taskInfo.windowingMode) {
+ WINDOWING_MODE_FREEFORM -> {
+ globalMenuPosition.set(
+ /* x = */ taskBounds.left + marginMenuStart,
+ /* y = */ taskBounds.top + marginMenuTop
+ )
+ }
+ WINDOWING_MODE_FULLSCREEN -> {
+ globalMenuPosition.set(
+ /* x = */ taskBounds.width() / 2 - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
+ val leftOrTopStageBounds = Rect()
+ val rightOrBottomStageBounds = Rect()
+ splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
+ // TODO(b/343561161): This needs to be calculated differently if the task is in
+ // top/bottom split.
+ when (splitPosition) {
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
+ globalMenuPosition.set(
+ /* x = */ leftOrTopStageBounds.width()
+ + (rightOrBottomStageBounds.width() / 2)
+ - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
+ globalMenuPosition.set(
+ /* x = */ (leftOrTopStageBounds.width() / 2)
+ - (menuWidth / 2),
+ /* y = */ marginMenuTop
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update pill layout, in case task changes have caused positioning to change.
+ */
+ fun relayout(t: SurfaceControl.Transaction) {
+ handleMenuViewContainer?.let { container ->
+ updateHandleMenuPillPositions()
+ container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y)
+ }
+ }
+
+ /**
+ * Check a passed MotionEvent if a click or hover has occurred on any button on this caption
+ * Note this should only be called when a regular onClick/onHover is not possible
+ * (i.e. the button was clicked through status bar layer)
+ *
+ * @param ev the MotionEvent to compare against.
+ */
+ fun checkMotionEvent(ev: MotionEvent) {
+ // If the menu view is above status bar, we can let the views handle input directly.
+ if (isViewAboveStatusBar) return
+ val handleMenu = handleMenuViewContainer?.view ?: return
+ val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
+ val inputPoint = translateInputToLocalSpace(ev)
+ val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
+ val action = ev.actionMasked
+ collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
+ collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
+ if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+ collapse.performClick()
+ }
+ }
+
+ // Translate the input point from display coordinates to the same space as the handle menu.
+ private fun translateInputToLocalSpace(ev: MotionEvent): PointF {
+ return PointF(
+ ev.x - handleMenuPosition.x,
+ ev.y - handleMenuPosition.y
+ )
+ }
+
+ /**
+ * A valid menu input is one of the following:
+ * An input that happens in the menu views.
+ * Any input before the views have been laid out.
+ *
+ * @param inputPoint the input to compare against.
+ */
+ fun isValidMenuInput(inputPoint: PointF): Boolean {
+ if (!viewsLaidOut()) return true
+ if (!isViewAboveStatusBar) {
+ return pointInView(
+ handleMenuViewContainer?.view,
+ inputPoint.x - handleMenuPosition.x,
+ inputPoint.y - handleMenuPosition.y
+ )
+ } else {
+ // Handle menu exists in a different coordinate space when added to WindowManager.
+ // Therefore we must compare the provided input coordinates to global menu coordinates.
+ // This includes factoring for split stage as input coordinates are relative to split
+ // stage position, not relative to the display as a whole.
+ val inputRelativeToMenu = PointF(
+ inputPoint.x - globalMenuPosition.x,
+ inputPoint.y - globalMenuPosition.y
+ )
+ if (splitScreenController.getSplitPosition(taskInfo.taskId)
+ == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ // TODO(b/343561161): This also needs to be calculated differently if
+ // the task is in top/bottom split.
+ val leftStageBounds = Rect()
+ splitScreenController.getStageBounds(leftStageBounds, Rect())
+ inputRelativeToMenu.x += leftStageBounds.width().toFloat()
+ }
+ return pointInView(
+ handleMenuViewContainer?.view,
+ inputRelativeToMenu.x,
+ inputRelativeToMenu.y
+ )
+ }
+ }
+
+ private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+ return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ /**
+ * Check if the views for handle menu can be seen.
+ */
+ private fun viewsLaidOut(): Boolean = handleMenuViewContainer?.view?.isLaidOut ?: false
+
+ /**
+ * Determines handle menu height based the max size and the visibility of pills.
+ */
+ private fun getHandleMenuHeight(): Int {
+ var menuHeight = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_height) + pillElevation
+ if (!shouldShowWindowingPill) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ if (!shouldShowBrowserPill) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+ menuHeight -= pillTopMargin
+ }
+ return menuHeight
+ }
+
+ private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) {
+ return 0
+ }
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
+
+ fun close() {
+ val after = {
+ handleMenuViewContainer?.releaseView()
+ handleMenuViewContainer = null
+ }
+ if (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ handleMenuAnimator?.animateCollapseIntoHandleClose(after)
+ } else {
+ handleMenuAnimator?.animateClose(after)
+ }
+ }
+
+ companion object {
+ private const val TAG = "HandleMenu"
+ private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 8c5d4a2c2ffb..e3d22342cc9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@ import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
+import android.widget.Button
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import com.android.wm.shell.R
@@ -72,6 +73,7 @@ class HandleMenuAnimator(
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
+ private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -80,6 +82,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -94,6 +97,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
+ animateOpenInBrowserPill()
runAnimations()
}
@@ -104,11 +108,12 @@ class HandleMenuAnimator(
*
* @param after runs after the animation finishes.
*/
- fun animateCollapseIntoHandleClose(after: Runnable) {
+ fun animateCollapseIntoHandleClose(after: () -> Unit) {
appInfoCollapseToHandle()
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -120,11 +125,12 @@ class HandleMenuAnimator(
* @param after runs after animation finishes.
*
*/
- fun animateClose(after: Runnable) {
+ fun animateClose(after: () -> Unit) {
appInfoPillCollapse()
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
+ openInBrowserPillClose()
runAnimations(after)
}
@@ -137,6 +143,7 @@ class HandleMenuAnimator(
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
+ openInBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -147,6 +154,9 @@ class HandleMenuAnimator(
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
+
+ openInBrowserPill.pivotX = menuWidth / 2f
+ openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -268,12 +278,50 @@ class HandleMenuAnimator(
// More Actions Content Opacity Animation
moreActionsPill.children.forEach {
animators +=
- ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_CONTENT_ALPHA_OPEN_DURATION
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+ }
+
+ private fun animateOpenInBrowserPill() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ startDelay = BODY_SCALE_OPEN_DELAY
+ duration = BODY_SCALE_OPEN_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ startDelay = BODY_ALPHA_OPEN_DELAY
+ duration = BODY_ALPHA_OPEN_DURATION
+ }
+
+ // Open in Browser Elevation Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ startDelay = ELEVATION_OPEN_DELAY
+ duration = BODY_ELEVATION_OPEN_DURATION
+ }
+
+ // Open in Browser Button Opacity Animation
+ val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ animators +=
+ ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_CONTENT_ALPHA_OPEN_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
- }
}
private fun appInfoPillCollapse() {
@@ -379,14 +427,45 @@ class HandleMenuAnimator(
}
}
+ private fun openInBrowserPillClose() {
+ // Open in Browser X & Y Scaling Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Open in Browser Opacity Animation
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+
+ // Upward Open in Browser y-translation Animation
+ val yStart: Float = -captionHeight / 2
+ animators +=
+ ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ duration = BODY_CLOSE_DURATION
+ }
+ }
+
/**
* Runs the list of hide animators concurrently.
*
* @param after runs after animation finishes.
*/
- private fun runAnimations(after: Runnable? = null) {
+ private fun runAnimations(after: (() -> Unit)? = null) {
runningAnimation?.apply {
- // Remove all listeners, so that after runnable isn't triggered upon cancel.
+ // Remove all listeners, so that the after function isn't triggered upon cancel.
removeAllListeners()
// If an animation runs while running animation is triggered, gracefully cancel.
cancel()
@@ -396,7 +475,7 @@ class HandleMenuAnimator(
playTogether(animators)
animators.clear()
doOnEnd {
- after?.run()
+ after?.invoke()
runningAnimation = null
}
start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 956d04c548f7..153221150992 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -33,9 +33,9 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
@@ -59,6 +59,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
@@ -67,22 +68,24 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions) {
+ Transitions transitions, InteractionJankMonitor interactionJankMonitor) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions);
+ SurfaceControl.Transaction::new, transitions, interactionJankMonitor);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+ InteractionJankMonitor interactionJankMonitor) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
+ mInteractionJankMonitor = interactionJankMonitor;
}
@Override
@@ -93,9 +96,8 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mRepositionStartPoint.set(x, y);
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
- InteractionJankMonitorUtils.beginTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW,
- mDesktopWindowDecoration.mContext, mDesktopWindowDecoration.mTaskSurface,
- /* tag= */ null);
+ mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
+ mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -153,7 +155,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
// won't be called.
resetVeilIfVisible();
}
- InteractionJankMonitorUtils.endTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
} else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
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 216990c35247..d212f2131ed4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -53,9 +54,10 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
@@ -105,7 +107,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
- final DisplayController mDisplayController;
+ final @NonNull DisplayController mDisplayController;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -158,7 +160,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
WindowDecoration(
Context context,
- DisplayController displayController,
+ @NonNull DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
@NonNull SurfaceControl taskSurface,
@@ -759,9 +761,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
void addOrUpdate(WindowContainerTransaction wct) {
- wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects);
+ final @InsetsSource.Flags int captionSourceFlags =
+ Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
+ wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
+ captionSourceFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
- mBoundingRects);
+ mBoundingRects, 0 /* flags */);
}
void remove(WindowContainerTransaction wct) {
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 6c2c8fd46bc9..4897f76a20cf 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor.additionalviewcontainer
import android.content.Context
import android.graphics.PixelFormat
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.View
@@ -45,9 +46,11 @@ class AdditionalSystemViewContainer(
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT
- )
- lp.title = "Additional view container of Task=$taskId"
- lp.setTrustedOverlay()
+ ).apply {
+ title = "Additional view container of Task=$taskId"
+ gravity = Gravity.LEFT or Gravity.TOP
+ setTrustedOverlay()
+ }
val wm: WindowManager? = context.getSystemService(WindowManager::class.java)
wm?.addView(view, lp)
}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index b8a19ad35307..a77fd51d5fcb 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -9,7 +9,7 @@ hwwang@google.com
chenghsiuchang@google.com
atsjenk@google.com
jorgegil@google.com
-nmusgrave@google.com
+vaniadesmonda@google.com
pbdr@google.com
tkachenkoi@google.com
mpodolian@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index a0a61fe2cf72..d0e8215e662e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -117,12 +117,10 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
/**
* Checks that all parts of the screen are covered at the start and end of the transition
- *
- * TODO b/197726599 Prevents all states from being checked
*/
@Presubmit
@Test
- fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
+ fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered()
/** Checks [pipApp] window remains visible and on top throughout the transition */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 35ed8de3a464..7873a85d515d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -65,7 +65,7 @@ open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 879034f32514..5c539a5e8a03 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -73,7 +73,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
setup {
standardAppHelper.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
standardAppHelper.enterFullscreen()
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index d485b82f5ddb..430f80b9a927 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -20,18 +20,23 @@ import android.tools.flicker.AssertionInvocationGroup
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
-import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
+import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
+import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
-import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
-import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop
+import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
import android.tools.flicker.extractors.ITransitionMatcher
import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
+import android.tools.flicker.extractors.TaggedCujTransitionMatcher
+import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder
+import android.tools.traces.events.CujType
import android.tools.traces.wm.Transition
import android.tools.traces.wm.TransitionType
@@ -48,6 +53,7 @@ class DesktopModeFlickerScenarios {
transitions: Collection<Transition>
): Collection<Transition> {
return transitions.filter {
+ // TODO(351168217) Use jank CUJ to extract a longer trace
it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
}
}
@@ -60,11 +66,14 @@ class DesktopModeFlickerScenarios {
AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
AppWindowHasDesktopModeInitialBoundsAtTheEnd(
Components.DESKTOP_MODE_APP
- )
+ ),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ // Use this scenario for closing an app in desktop windowing, except the last app. For the
+ // last app use CLOSE_LAST_APP scenario
val CLOSE_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_APP"),
@@ -75,7 +84,13 @@ class DesktopModeFlickerScenarios {
override fun findAll(
transitions: Collection<Transition>
): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.CLOSE }
+ // In case there are multiple windows closing, filter out the
+ // last window closing. It should use the CLOSE_LAST_APP
+ // scenario below.
+ return transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .sortedByDescending { it.id }
+ .drop(1)
}
}
),
@@ -100,19 +115,19 @@ class DesktopModeFlickerScenarios {
transitions: Collection<Transition>
): Collection<Transition> {
val lastTransition =
- transitions.findLast { it.type == TransitionType.CLOSE }
- return if (lastTransition != null) listOf(lastTransition)
- else emptyList()
+ transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
}
}
),
assertions =
AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowMovesToTop()
+ AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
)
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -121,24 +136,29 @@ class DesktopModeFlickerScenarios {
FlickerConfigEntry(
scenarioId = ScenarioId("CORNER_RESIZE"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
- it.type == TransitionType.CHANGE
- }
- }
- }
- ),
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
+ )
+
+ val CORNER_RESIZE_TO_MINIMUM_SIZE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ )
+ .build(),
assertions =
- listOf(
- AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
- ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700))
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
new file mode 100644
index 000000000000..6319cf74ed8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in
+ * landscape mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizeLandscape : ResizeAppWithCornerResize(
+ rotation = Rotation.ROTATION_90,
+ horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
new file mode 100644
index 000000000000..431f6e3d3ea2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in portrait mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizePortrait : ResizeAppWithCornerResize(horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
new file mode 100644
index 000000000000..0b6c9af17e7a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+
+@Ignore("Base Test Class")
+abstract class ExitDesktopWithDragToTopDragZone
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun exitDesktopWithDragToTopDragZone() {
+ testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
new file mode 100644
index 000000000000..20e2167c28f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base scenario test for maximize app window CUJ in desktop mode. */
+@Ignore("Base Test Class")
+abstract class MaximizeAppWindow
+{
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
+ Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWindow() {
+ testApp.maximiseDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
index ac9089a5c1bd..136cf378aa09 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
@@ -38,7 +38,9 @@ import org.junit.Test
@Ignore("Base Test Class")
abstract class ResizeAppWithCornerResize
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+ val horizontalChange: Int = 50,
+ val verticalChange: Int = -50) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
@@ -46,7 +48,9 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@Before
fun setup() {
@@ -58,7 +62,11 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun resizeAppWithCornerResize() {
- testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50)
+ testApp.cornerResize(wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.RIGHT_TOP,
+ horizontalChange,
+ verticalChange)
}
@After
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index c671fbe39ac5..b5a6d83afd05 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -48,6 +48,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ // TODO: b/349075982 - Remove once launcher rotation and checks are stable.
+ tapl.setExpectedRotationCheckEnabled(false)
SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
index 3f2603aec86a..35b2f56bca92 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -107,7 +107,7 @@ android_test {
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
],
static_libs: [
"WMShellFlickerTestsBase",
@@ -124,7 +124,7 @@ android_test {
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
test_config_template: "AndroidTestTemplate.xml",
srcs: [
- ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup3-src",
],
static_libs: [
"WMShellFlickerTestsBase",
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 92be4f9f0374..6b6954289a34 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -59,6 +59,7 @@ android_test {
"guava-android-testlib",
"com.android.window.flags.window-aconfig-java",
"platform-test-annotations",
+ "flag-junit",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 8303317d39fc..e91828be4ebe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -45,6 +45,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -63,6 +64,8 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -70,6 +73,7 @@ import com.android.wm.shell.sysui.ShellInit;
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;
@@ -371,7 +375,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
clearInvocations(mCompatUI);
@@ -381,7 +385,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Not show size compat UI if task is not visible.
clearInvocations(mCompatUI);
@@ -391,11 +395,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -410,7 +414,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
// Task listener sent to compat UI is null if top activity isn't eligible for letterbox
// education.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null if top activity is eligible for letterbox education and task
// is visible.
@@ -421,7 +425,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Task listener is null if task is invisible.
clearInvocations(mCompatUI);
@@ -431,11 +435,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -451,7 +455,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
// Task listener sent to compat UI is null if top activity doesn't request a camera
// compat control.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null when request a camera compat control for a visible task.
clearInvocations(mCompatUI);
@@ -462,7 +466,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// CompatUIController#onCompatInfoChanged is called when requested state for a camera
// compat control changes for a visible task.
@@ -474,7 +478,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo3.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, taskListener);
// CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
// mode for a visible task that has a compat control.
@@ -487,7 +491,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo4.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo4);
- verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo4, taskListener);
// Task linster is null when a camera compat control is dimissed for a visible task.
clearInvocations(mCompatUI);
@@ -498,7 +502,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_DISMISSED;
taskInfo5.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo5);
- verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo5, null /* taskListener */);
// Task linster is null when request a camera compat control for a invisible task.
clearInvocations(mCompatUI);
@@ -509,11 +513,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo6.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo6);
- verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo6, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -640,7 +644,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId);
+ mOrganizer.onSizeCompatRestartButtonClicked(
+ new CompatUIEvents.SizeCompatRestartButtonClicked(task1.taskId));
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
@@ -713,4 +718,13 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo.isVisible = true;
return taskInfo;
}
+
+ private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener listener) {
+ final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class);
+ verify(mCompatUI).onCompatInfoChanged(capture.capture());
+ final CompatUIInfo captureValue = capture.getValue();
+ assertEquals(captureValue.getTaskInfo(), taskInfo);
+ assertEquals(captureValue.getListener(), listener);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 636c6326d213..f5847cc27071 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -70,7 +70,7 @@ public class DividerViewTest extends ShellTestCase {
mContext,
configuration, mCallbacks);
splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
- mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ mDividerView = spy(splitWindowManager.getDividerView());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 4cd2a366f5eb..ecaf970ae389 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.compatui
+import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.R
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import org.junit.Assert.assertFalse
@@ -34,26 +36,55 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {
-
@Test
- fun testIsSingleTopActivityTranslucent() {
- assertTrue(isSingleTopActivityTranslucent(
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = true
numActivities = 1
}))
- assertFalse(isSingleTopActivityTranslucent(
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = true
numActivities = 0
}))
- assertFalse(isSingleTopActivityTranslucent(
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }))
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
isTopActivityTransparent = false
numActivities = 1
}))
}
-} \ No newline at end of file
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityStyleFloating = true
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() {
+ val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = baseComponent
+ }))
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 9c008647104a..fc7a7770b8ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -38,6 +38,9 @@ import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -45,6 +48,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
@@ -55,6 +59,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -63,6 +68,7 @@ import dagger.Lazy;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,6 +88,10 @@ public class CompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
@@ -168,28 +178,32 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
// Verify that the compat controls are added with non-null task listener.
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -202,7 +216,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -213,9 +227,9 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- /* taskListener= */ null);
+ /* taskListener= */ null));
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
@@ -223,6 +237,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
@@ -230,7 +245,7 @@ public class CompatUIControllerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -240,7 +255,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -253,6 +268,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
@@ -260,7 +276,7 @@ public class CompatUIControllerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -270,7 +286,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -282,7 +298,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -296,6 +312,7 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayAdded() {
mController.onDisplayAdded(DISPLAY_ID);
mController.onDisplayAdded(DISPLAY_ID + 1);
@@ -305,11 +322,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -328,9 +345,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayConfigurationChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
@@ -346,10 +364,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
@@ -373,9 +392,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnImeShowHide() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
@@ -387,7 +407,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -405,9 +425,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after keyguard becomes showing.
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -419,7 +440,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -437,9 +458,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -466,9 +488,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -495,6 +518,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutRecreatedIfNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -502,14 +526,15 @@ public class CompatUIControllerTest extends ShellTestCase {
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(2))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutNotRecreatedIfNotNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -517,14 +542,15 @@ public class CompatUIControllerTest extends ShellTestCase {
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(1))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() {
// Simulate user aspect ratio button being shown for previous task
mController.setHasShownUserAspectRatioSettingsButton(true);
@@ -545,6 +571,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -606,6 +633,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -634,6 +662,7 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -674,7 +703,7 @@ public class CompatUIControllerTest extends ShellTestCase {
CAMERA_COMPAT_CONTROL_HIDDEN);
taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index cd3e8cb0e8e1..33d69f5c34c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -23,6 +23,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_S
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@@ -31,6 +32,9 @@ import android.app.ActivityManager;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,16 +44,20 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -70,8 +78,12 @@ public class CompatUILayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@@ -101,6 +113,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForRestartButton() {
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
@@ -117,6 +130,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForRestartButton() {
doNothing().when(mWindowManager).onRestartButtonLongClicked();
@@ -127,6 +141,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -137,6 +152,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -145,16 +161,17 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -163,16 +180,17 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -181,12 +199,12 @@ public class CompatUILayoutTest extends ShellTestCase {
button.performClick();
verify(mWindowManager).onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show */ false);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraTreatmentButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -198,6 +216,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraDismissButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -208,6 +227,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForCameraCompatHint() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -229,4 +249,15 @@ public class CompatUILayoutTest extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 41a81c1a9921..eb3da8f65b5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -25,6 +25,7 @@ import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -42,6 +43,9 @@ import android.app.CameraCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -60,6 +64,8 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import junit.framework.Assert;
@@ -85,12 +91,16 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int TASK_ID = 1;
private static final int TASK_WIDTH = 2000;
private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@@ -129,6 +139,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
@@ -174,6 +185,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateCameraCompatControl() {
// Doesn't create layout if show is false.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -212,6 +224,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -224,6 +237,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -315,6 +329,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.createLayout(/* canShow= */ false);
@@ -347,6 +362,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -366,6 +382,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -390,6 +407,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.mLayout = null;
@@ -415,6 +433,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -423,37 +442,38 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show= */ false);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraTreatmentButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
@@ -468,8 +488,9 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonLongClicked_showHint() {
- // Not create hint popup.
+ // Not create hint popup.
mWindowManager.mHasSizeCompat = true;
mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -483,6 +504,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraControlLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -498,6 +520,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
@@ -506,6 +529,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testShouldShowSizeCompatRestartButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
@@ -558,4 +582,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index 172c263ab0f6..e8191db13084 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,6 +37,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +60,10 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
private View mDismissButton;
private View mDialogContainer;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -66,6 +76,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_education_dialog_container));
@@ -76,6 +87,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -83,6 +95,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClicked() {
assertTrue(mLayout.performClick());
@@ -90,6 +103,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -97,6 +111,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index a60a1cbb435f..b5664ac113fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.compatui;
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +38,9 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -61,6 +65,7 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -116,6 +121,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -153,6 +162,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -162,6 +172,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, /* isDocked */ true);
@@ -172,6 +183,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -182,6 +194,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -192,6 +205,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -238,6 +252,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ false);
@@ -271,6 +286,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -288,6 +304,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -316,6 +333,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -329,6 +347,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_canShowFalse_doesNothing() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -343,6 +362,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -364,6 +384,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease_animationIsCancelled() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -374,6 +395,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testDeviceThemeChange_educationDialogUnseen_recreated() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 4f71b83179b1..0da14d673732 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -23,6 +25,9 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.TaskInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -34,6 +39,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -62,6 +68,10 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
@Mock
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -74,6 +84,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertNotNull(mMoveUpButton);
assertNotNull(mMoveDownButton);
@@ -82,6 +93,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
false, /* letterboxVerticalPosition */
@@ -94,6 +106,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
@@ -106,6 +119,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 5867a8553d53..eafb41470cda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -35,6 +40,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -65,6 +71,10 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -80,6 +90,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
@@ -87,6 +98,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -97,6 +109,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
index e2dcdb0e91b2..6b0c5dd2e1c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +39,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@ public class RestartDialogLayoutTest extends ShellTestCase {
private View mDialogContainer;
private CheckBox mDontRepeatCheckBox;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_restart_dialog_container));
@@ -86,6 +97,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -93,6 +105,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithoutCheckbox() {
mDontRepeatCheckBox.setChecked(false);
assertTrue(mRestartButton.performClick());
@@ -101,6 +114,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithCheckbox() {
mDontRepeatCheckBox.setChecked(true);
assertTrue(mRestartButton.performClick());
@@ -109,6 +123,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClickedDoesntDismiss() {
assertFalse(mLayout.performClick());
@@ -116,6 +131,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -124,6 +140,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
@@ -135,6 +152,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetRestartOnClickListenerNull() {
mLayout.setRestartOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
index 9f109a1d0f50..cfeef90cb4b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -33,6 +38,7 @@ import com.android.wm.shell.transition.Transitions;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
private RestartDialogWindowManager mWindowManager;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -86,6 +97,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 02316125bcc3..3fa21cee0d68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.compatui;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -28,6 +29,9 @@ import android.app.ActivityManager;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -47,6 +51,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -86,6 +91,10 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
private UserAspectRatioSettingsLayout mLayout;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -107,6 +116,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsButton() {
final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
button.performClick();
@@ -123,6 +133,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForUserAspectRatioButton() {
doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
@@ -133,6 +144,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsHint() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 94e168ed70ed..9f288cc4bd93 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -22,6 +22,7 @@ import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,6 +40,9 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
@@ -61,6 +65,7 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -107,6 +112,10 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -138,6 +147,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateUserAspectRatioButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -178,6 +188,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -190,6 +201,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -242,6 +254,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ false);
@@ -267,6 +280,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -292,6 +306,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -310,6 +325,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -329,6 +345,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -353,6 +370,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.removeLayout();
@@ -378,6 +396,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutHasUserAspectRatioSettingsButton() {
clearInvocations(mWindowManager);
spyOn(mWindowManager);
@@ -411,6 +430,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -419,6 +439,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonClicked() {
mWindowManager.onUserAspectRatioSettingsButtonClicked();
@@ -433,6 +454,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -448,6 +470,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
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 665bed0c8a88..d4596396840e 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
@@ -52,7 +52,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
-import com.android.wm.shell.shared.DesktopModeStatus
+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
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 5f36e9a5e7b3..8558a77e4e7e 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
@@ -20,6 +20,7 @@ import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -89,7 +90,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -137,6 +138,7 @@ import org.mockito.quality.Strictness
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
@@ -193,7 +195,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(true)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
shellInit = spy(ShellInit(testExecutor))
@@ -263,8 +264,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
+ fun instantiate_canNotEnterDesktopMode_doNotAddInitCallback() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
createController()
@@ -310,6 +311,25 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2 (no wallpaper intent)
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
@@ -329,6 +349,25 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
@@ -426,6 +465,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
@@ -435,10 +475,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: wallpaper intent, task
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Move freeform task to front
+ wct.assertReorderAt(index = 2, taskDefaultDisplay)
}
@Test
@@ -463,7 +506,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -473,11 +516,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
// Reorder freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ wct.assertReorderAt(index = 2, freeformTask, toTop = true)
}
@Test
@@ -700,19 +745,37 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun moveToDesktop_topActivityTranslucent_doesNothing() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
val task =
- setUpFullscreenTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
- }
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
+
+ controller.moveToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+ val task =
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
+ numActivities = 1
+ }
controller.moveToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun moveToDesktop_systemUIActivity_doesNothing() {
val task = setUpFullscreenTask()
@@ -842,16 +905,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+ assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+ // Move home to front
+ wct.assertReorderAt(0, homeTask)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ // Bring freeform tasks to front
wct.assertReorderSequenceInRange(
- range = 1..<(taskLimit + 1),
+ range = 2..<(taskLimit + 2),
*freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
newTask
)
@@ -869,6 +935,24 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -880,6 +964,44 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -1087,13 +1209,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val fullscreenTask = createFullscreenTask()
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ assertThat(wct.hierarchyOps).hasSize(2)
+ wct.assertReorderAt(1, homeTask, toTop = false)
}
@Test
@@ -1368,20 +1496,40 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
val task =
- setUpFreeformTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
- }
+ setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+ val task =
+ setUpFreeformTask().apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
val task = setUpFreeformTask()
@@ -1677,6 +1825,49 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
@@ -1885,6 +2076,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
eq(null))
}
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -1894,14 +2086,67 @@ class DesktopTasksControllerTest : ShellTestCase() {
task2.isFocused = true
task3.isFocused = false
- controller.enterSplit(DEFAULT_DISPLAY, false)
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
verify(splitScreenController)
.requestEnterSplitSelect(
- task2,
+ eq(task2),
any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ }
+
+ @Test
+ fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Removes wallpaper activity when leaving desktop
+ wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.value.hierarchyOps).isEmpty()
}
@Test
@@ -1916,6 +2161,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+ val bounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo = ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = false
+ }
+
+ // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+ val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+ controller.toggleDesktopTaskSize(task)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ }
+
+ @Test
fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
@@ -1942,6 +2207,46 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
+ boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
+ isResizeable = false
+ }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
+ STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
val boundsBeforeMaximize = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
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 77f917cc28d8..8d9fd9166050 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
@@ -24,6 +24,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -32,7 +33,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
@@ -205,6 +206,46 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+ desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
+ desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY, wct)
+
+ assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[0].container).isEqualTo(task1.token.asBinder())
+ assertThat(wct.hierarchyOps[1].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[1].container).isEqualTo(task2.token.asBinder())
+ }
+
+ @Test
fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
(1..<taskLimit).forEach { _ -> setUpFreeformTask() }
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 b2467e9a62cf..e5157c974e2d 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
@@ -45,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -65,6 +66,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
@Mock
private Transitions mTransitions;
@Mock
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
IBinder mToken;
@Mock
Supplier<SurfaceControl.Transaction> mTransactionFactory;
@@ -94,7 +97,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext);
+ mContext, mInteractionJankMonitor);
mPoint = new Point(0, 0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 582fb91559e5..97fa8d6ceca9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -289,9 +289,9 @@ public class DragAndDropPolicyTest extends ShellTestCase {
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(SPLIT_POSITION_UNDEFINED), any(), any());
}
private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
@@ -304,14 +304,14 @@ public class DragAndDropPolicyTest extends ShellTestCase {
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
@@ -324,14 +324,15 @@ public class DragAndDropPolicyTest extends ShellTestCase {
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
+ null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
@Test
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 3f3cafcf6375..6ec6bed860bd 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
@@ -36,7 +36,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 5c5a1a26f5d2..a0aab2e6c8a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,7 +68,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
new file mode 100644
index 000000000000..b1d62f485a2a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -0,0 +1,456 @@
+/*
+ * 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.desktopmode
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.Companion.convertToToggleOverrideWithFallback
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test class for [DesktopModeFlags]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeFlagsTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeFlagsTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Before
+ fun setUp() {
+ resetCache()
+ }
+
+ // TODO(b/348193756): Add tests
+ // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and
+ // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non
+ // overridable flags to DesktopModeFlags.
+
+ @Test
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // In absence of dev options, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // In absence of dev options, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For overridableFlag, for unset overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For overridableFlag, for unset overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noOverride_featureFlagOn_returnsTrue() {
+ setOverride(null)
+
+ // For overridableFlag, in absence of overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noOverride_featureFlagOff_returnsFalse() {
+ setOverride(null)
+
+ // For overridableFlag, in absence of overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() {
+ setOverride(-2)
+
+ // For overridableFlag, for recognizable overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() {
+ setOverride(-2)
+
+ // For overridableFlag, for recognizable overrides, follow flag
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOff_featureFlagOn_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOn_featureFlagOff_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+
+ setOverride(OVERRIDE_ON.setting)
+
+ // Keep overrides constant through the process
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // For overridableFlag, follow override if they exist
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Keep overrides constant through the process
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_ON.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_ON.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_UNSET.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() {
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ setOverride(OVERRIDE_UNSET.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if not present
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc")
+ setOverride(OVERRIDE_OFF.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if currently invalid
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2")
+ setOverride(OVERRIDE_OFF.setting)
+
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ // Store System Property if currently invalid
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString())
+ setOverride(OVERRIDE_ON.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_OFF.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString())
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_ON.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Suppress("ktlint:standard:max-line-length")
+ fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() {
+ System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString())
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Have a consistent override until reboot
+ assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
+ assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY))
+ .isEqualTo(OVERRIDE_UNSET.setting.toString())
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOff_returnFalse() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOn_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_UNSET.setting)
+
+ // For unset overrides, follow flag
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOff_returnTrue() {
+ setOverride(OVERRIDE_ON.setting)
+
+ // Follow override if they exist, and is not equal to default toggle state (dw flag)
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ @DisableFlags(
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOff_returnsFalse() {
+ setOverride(OVERRIDE_OFF.setting)
+
+ // When toggle override matches its default state (dw flag), don't override flags
+ assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse()
+ }
+
+ @Test
+ fun convertToToggleOverrideWithFallback_validInt_returnsToggleOverride() {
+ assertThat(convertToToggleOverrideWithFallback(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF)
+ assertThat(convertToToggleOverrideWithFallback(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET)
+ }
+
+ @Test
+ fun convertToToggleOverrideWithFallback_invalidInt_returnsFallback() {
+ assertThat(convertToToggleOverrideWithFallback(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON)
+ assertThat(convertToToggleOverrideWithFallback(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET)
+ }
+
+ private fun setOverride(setting: Int?) {
+ val contentResolver = mContext.contentResolver
+ val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+ if (setting == null) {
+ Settings.Global.putString(contentResolver, key, null)
+ } else {
+ Settings.Global.putInt(contentResolver, key, setting)
+ }
+ }
+
+ private fun resetCache() {
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, null)
+
+ // Clear override cache stored in System property
+ System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
+ }
+
+ private companion object {
+ const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
new file mode 100644
index 000000000000..2fabd4a33586
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS
@@ -0,0 +1 @@
+file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 3c387f0d7c34..5b95b1588814 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -49,6 +50,9 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
+import android.os.IBinder;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -195,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@@ -213,19 +217,20 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -233,15 +238,16 @@ public class SplitScreenControllerTests extends ShellTestCase {
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
- doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@@ -249,7 +255,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -261,13 +267,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
// Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
- .findTaskInBackground(any(), anyInt());
+ .findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
}
@Test
@@ -284,7 +290,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
@@ -312,6 +318,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
info.supportsMultiWindow = true;
info.baseIntent = strIntent;
info.baseActivity = strIntent.getComponent();
+ info.token = new WindowContainerToken(mock(IWindowContainerToken.class));
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 754a173ff069..6bc7e499159c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -185,6 +186,73 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
verify(startT, never()).setColor(any(), any());
}
+ @Test
+ public void startAnimation_freeformOpenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeformMinimizeChange_underFullscreenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeform_minimizeAnimation_reparentsTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT).reparent(any(), any());
+ }
+
private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
handler.mergeAnimation(
new Binder(),
@@ -195,10 +263,14 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
}
private static RunningTaskInfo createTaskInfo(int taskId) {
+ return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
taskInfo.token = mock(WindowContainerToken.class);
return taskInfo;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 8331d591fd59..409b87723e79 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1191,8 +1191,7 @@ public class ShellTransitionTests extends ShellTestCase {
mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
new RecentsTransitionHandler(shellInit, transitions,
- mock(RecentTasksController.class), mock(HomeTransitionObserver.class),
- () -> mock(SurfaceControl.Transaction.class));
+ mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
new file mode 100644
index 000000000000..bad14bbdb141
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_TRANSLUCENT
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+class TransitionAnimationHelperTest : ShellTestCase() {
+
+ @Mock
+ lateinit var transitionAnimation: TransitionAnimation
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_returnsMinimizeAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskToFront_taskToFrontChange_returnsUnminimizeAnim() {
+ val toFrontChange = ChangeBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .addChange(toFrontChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_TO_FRONT, info, toFrontChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_fullscreen_taskOpen_returnsTaskOpenEnterAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN).addChange(openChange).build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, openChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_taskOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_passesTranslucent() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation),
+ /* translucent= */ eq(true))
+ }
+
+ private fun loadAttributeAnimation(
+ @WindowManager.TransitionType type: Int,
+ info: TransitionInfo,
+ change: TransitionInfo.Change,
+ wallpaperTransit: Int = TransitionAnimation.WALLPAPER_TRANSITION_NONE,
+ isDreamTransition: Boolean = false,
+ ) {
+ TransitionAnimationHelper.loadAttributeAnimation(
+ type, info, change, wallpaperTransit, transitionAnimation, isDreamTransition)
+ }
+
+ private fun createTaskInfo(windowingMode: Int): RunningTaskInfo {
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setWindowingMode(windowingMode)
+ .build()
+ return taskInfo
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
new file mode 100644
index 000000000000..261d4b5e7d1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -0,0 +1,107 @@
+/*
+ * 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
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.WindowInsetsController
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CaptionWindowDecorationTests : ShellTestCase() {
+ @Test
+ fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
+ val taskInfo = createTaskInfo()
+ taskInfo.configuration.windowConfiguration.windowingMode =
+ WindowConfiguration.WINDOWING_MODE_FREEFORM
+ taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance =
+ WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
+ val relayoutParams = WindowDecoration.RelayoutParams()
+
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+
+ Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
+ }
+
+ @Test
+ fun updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
+ val taskInfo = createTaskInfo()
+ taskInfo.configuration.windowConfiguration.windowingMode =
+ WindowConfiguration.WINDOWING_MODE_FREEFORM
+ taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = 0
+ val relayoutParams = WindowDecoration.RelayoutParams()
+
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+
+ Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
+ }
+
+ @Test
+ fun updateRelayoutParams_addOccludingCaptionElementCorrectly() {
+ val taskInfo = createTaskInfo()
+ val relayoutParams = WindowDecoration.RelayoutParams()
+ CaptionWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ taskInfo,
+ true,
+ false
+ )
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
+ WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.START)
+ Truth.assertThat(relayoutParams.mOccludingCaptionElements[1].mAlignment).isEqualTo(
+ WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.END)
+ }
+
+ private fun createTaskInfo(): ActivityManager.RunningTaskInfo {
+ val taskDescriptionBuilder =
+ ActivityManager.TaskDescription.Builder()
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build()
+ taskInfo.realActivity = ComponentName(
+ "com.android.wm.shell.windowdecor",
+ "CaptionWindowDecorationTests"
+ )
+ taskInfo.baseActivity = ComponentName(
+ "com.android.wm.shell.windowdecor",
+ "CaptionWindowDecorationTests"
+ )
+ return taskInfo
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 4c94c2933383..b1803e97b107 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
@@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -69,7 +70,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -134,6 +135,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -167,7 +169,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockInputMonitorFactory,
transactionFactory,
mockRootTaskDisplayAreaOrganizer,
- windowDecorByTaskIdSpy
+ windowDecorByTaskIdSpy, mockInteractionJankMonitor
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -346,10 +348,35 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testDecorationIsNotCreatedForTopTranslucentActivities() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
+ }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
isTopActivityTransparent = true
+ isTopActivityStyleFloating = false
numActivities = 1
}
onTaskOpening(task)
@@ -359,6 +386,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForSystemUIActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
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 36e8a4671a46..d8606093ac5c 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
@@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -50,6 +51,7 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
@@ -82,7 +84,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
@@ -118,6 +120,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
+ private static final Uri TEST_URI = Uri.parse("www.google.com");
+
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
@@ -150,6 +154,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private PackageManager mMockPackageManager;
@Mock
private Handler mMockHandler;
+ @Mock
+ private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -170,7 +176,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Before
- public void setUp() {
+ public void setUp() throws PackageManager.NameNotFoundException {
mMockitoSession = mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus.class)
@@ -186,6 +192,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mTestableContext.ensureTestableResources();
mContext.setMockPackageManager(mMockPackageManager);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel");
+ final ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo);
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
@@ -552,6 +561,65 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(mMockHandler).removeCallbacks(any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ // Asset runnable posted to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkNotResetToSameLink() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+ // Set captured link and run on captured link expired runnable
+ decor.relayout(taskInfo);
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ decor.relayout(taskInfo);
+ // Assert captured link not set to same value twice
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkExpiresAfterClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ // Assert captured link is set
+ assertTrue(decor.browserLinkAvailable());
+ decor.onOpenInBrowserClick();
+ //Assert Captured link expires after button is clicked
+ assertFalse(decor.browserLinkAvailable());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_openInBrowserListenerCalledOnClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+
+ decor.relayout(taskInfo);
+ decor.onOpenInBrowserClick();
+
+ verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ }
+
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
final OnTaskActionClickListener l = (taskId, tag) -> {};
decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -592,11 +660,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
+ windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
return windowDecor;
}
@@ -608,12 +676,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(visible)
.build();
- taskInfo.topActivityInfo = new ActivityInfo();
- taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo();
taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
+ taskInfo.capturedLink = TEST_URI;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index ac5aeec3adc8..e52971120478 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -34,7 +34,7 @@ import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 4dea5a75a0e8..6a94cd8aa283 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -55,8 +55,6 @@ public class DragResizeWindowGeometryTests {
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
- private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
- + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -91,14 +89,13 @@ public class DragResizeWindowGeometryTests {
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
+ .addEqualityGroup(
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
- .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5))
.testEquals();
}
@@ -124,21 +121,21 @@ public class DragResizeWindowGeometryTests {
private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is still contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
// Vertically along the edge is contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
}
/**
@@ -151,10 +148,7 @@ public class DragResizeWindowGeometryTests {
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
GEOMETRY.union(region);
- // Make sure we're choosing a point outside of any debug region buffer.
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
@@ -168,9 +162,7 @@ public class DragResizeWindowGeometryTests {
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = DragResizeWindowGeometry.DEBUG
- ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
- : LARGE_CORNER_SIZE / 2;
+ final int cornerRadius = FINE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 5582e0f46321..adda9a688172 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -22,10 +22,10 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.Color
+import android.graphics.Point
import android.graphics.Rect
-import android.platform.test.annotations.RequiresFlagsEnabled
-import android.platform.test.flag.junit.CheckFlagsRule
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -33,6 +33,7 @@ import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
+import androidx.core.graphics.toPointF
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.R
@@ -46,6 +47,7 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UND
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
+import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -69,7 +71,7 @@ import org.mockito.kotlin.whenever
class HandleMenuTest : ShellTestCase() {
@JvmField
@Rule
- val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
@Mock
private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
@@ -92,6 +94,8 @@ class HandleMenuTest : ShellTestCase() {
private lateinit var handleMenu: HandleMenu
+ private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
+
@Before
fun setUp() {
val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -100,7 +104,7 @@ class HandleMenuTest : ShellTestCase() {
) {
SurfaceControl.Transaction()
}
- val menuView = LayoutInflater.from(context).inflate(
+ val menuView = LayoutInflater.from(mContext).inflate(
R.layout.desktop_mode_window_decor_handle_menu, null)
whenever(mockDesktopWindowDecoration.addWindow(
anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
@@ -110,50 +114,69 @@ class HandleMenuTest : ShellTestCase() {
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.isLandscape).thenReturn(true)
- mockDesktopWindowDecoration.mDecorWindowContext = context
+ mContext.orCreateTestableResources.apply {
+ addOverride(R.dimen.desktop_mode_handle_menu_width, MENU_WIDTH)
+ addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
+ addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
+ addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
+ addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
+ addOverride(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
+ }
+ mockDesktopWindowDecoration.mDecorWindowContext = mContext
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFullscreenMenuUsesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED)
val handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
- assertTrue(handleMenu.mHandleMenuPosition.equals(16f, -512f))
+ val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testFreeformMenu_usesViewHostViewContainer() {
createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalViewHostViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer)
// Verify menu is created near top-left of task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(12f, 8f))
+ val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN)
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitLeftMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
- // show at the top of split left task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(-624f, -512f))
+ // show at the top-center of split left task.
+ val expected = Point(
+ SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+ @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
fun testSplitRightMenu_usesSystemViewContainer() {
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT)
handleMenu = createAndShowHandleMenu()
- assertTrue(handleMenu.mHandleMenuViewContainer is AdditionalSystemViewContainer)
+ assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
- // show at the top of split right task.
- assertTrue(handleMenu.mHandleMenuPosition.equals(656f, -512f))
+ // show at the top-center of split right task.
+ val expected = Point(
+ SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
+ assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
private fun createTaskInfo(windowingMode: Int, splitPosition: Int) {
@@ -178,14 +201,10 @@ class HandleMenuTest : ShellTestCase() {
.setBounds(bounds)
.setVisible(true)
.build()
- // Calculate captionX similar to how WindowDecoration calculates it.
- whenever(mockDesktopWindowDecoration.captionX).thenReturn(
- (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .bounds.width() - context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_decor_caption_width)) / 2)
whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition)
whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer {
(it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS)
+ (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS)
}
}
@@ -193,12 +212,12 @@ class HandleMenuTest : ShellTestCase() {
val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
R.layout.desktop_mode_app_header
} else {
- R.layout.desktop_mode_app_header
+ R.layout.desktop_mode_app_handle
}
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
- splitScreenController, true /* shouldShowWindowingPill */,
- 50 /* captionHeight */ )
+ onClickListener, onTouchListener, appIcon, appName, displayController,
+ splitScreenController, true /* shouldShowWindowingPill */,
+ true /* shouldShowBrowserPill */, 50 /* captionHeight */)
handleMenu.show()
return handleMenu
}
@@ -208,5 +227,11 @@ class HandleMenuTest : ShellTestCase() {
private val FREEFORM_BOUNDS = Rect(500, 500, 2000, 1200)
private val SPLIT_LEFT_BOUNDS = Rect(0, 0, 1280, 1600)
private val SPLIT_RIGHT_BOUNDS = Rect(1280, 0, 2560, 1600)
+ private const val MENU_WIDTH = 200
+ private const val MENU_HEIGHT = 400
+ private const val MENU_TOP_MARGIN = 10
+ private const val MENU_START_MARGIN = 20
+ private const val MENU_PILL_ELEVATION = 2
+ private const val MENU_PILL_SPACING_MARGIN = 4
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 901ca90b573e..943c313e5b40 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -33,6 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
@@ -104,6 +105,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
private lateinit var mockContext: Context
@Mock
private lateinit var mockResources: Resources
+ @Mock
+ private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -150,7 +153,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions
+ mockTransitions,
+ mockInteractionJankMonitor
)
}
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 f3603e1d9b46..2d1bf14ffbb3 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
@@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -54,6 +56,8 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
@@ -71,15 +75,17 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -105,6 +111,9 @@ public class WindowDecorationTests extends ShellTestCase {
private static final int CORNER_RADIUS = 20;
private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -260,7 +269,8 @@ public class WindowDecorationTests extends ShellTestCase {
eq(0 /* index */),
eq(WindowInsets.Type.captionBar()),
eq(new Rect(100, 300, 400, 364)),
- any());
+ any(),
+ anyInt());
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -565,9 +575,9 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -654,9 +664,9 @@ public class WindowDecorationTests extends ShellTestCase {
// Never added.
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
// No need to remove them if they were never added.
verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token),
any(), eq(0) /* index */, eq(captionBar()));
@@ -681,9 +691,9 @@ public class WindowDecorationTests extends ShellTestCase {
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
windowDecor.relayout(taskInfo);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
windowDecor.close();
@@ -738,9 +748,9 @@ public class WindowDecorationTests extends ShellTestCase {
// Insets should be applied twice.
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
}
@Test
@@ -765,9 +775,30 @@ public class WindowDecorationTests extends ShellTestCase {
// Insets should only need to be applied once.
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any());
+ eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
+ eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void testRelayout_captionInsetForceConsume() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+ final WindowContainerToken token = TestRunningTaskInfoBuilder.createMockWCToken();
+ final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true);
+
+ final ActivityManager.RunningTaskInfo taskInfo =
+ builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ windowDecor.relayout(taskInfo);
+
+ // Caption inset source should be force-consuming.
+ verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
+ eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
}
@Test
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index 1cb8df311c89..629f14683b19 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -132,7 +132,7 @@ bool StringPool::StyleRef::operator==(const StyleRef& rhs) const {
auto rhs_iter = rhs.entry_->spans.begin();
for (const Span& span : entry_->spans) {
- const Span& rhs_span = *rhs_iter;
+ const Span& rhs_span = *rhs_iter++;
if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char ||
span.name != rhs_span.name) {
return false;
@@ -297,24 +297,22 @@ void StringPool::Prune() {
template <typename E>
static void SortEntries(
std::vector<std::unique_ptr<E>>& entries,
- const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
+ base::function_ref<int(const StringPool::Context&, const StringPool::Context&)> cmp) {
using UEntry = std::unique_ptr<E>;
+ std::sort(entries.begin(), entries.end(), [cmp](const UEntry& a, const UEntry& b) -> bool {
+ int r = cmp(a->context, b->context);
+ if (r == 0) {
+ r = a->value.compare(b->value);
+ }
+ return r < 0;
+ });
+}
- if (cmp != nullptr) {
- std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
- int r = cmp(a->context, b->context);
- if (r == 0) {
- r = a->value.compare(b->value);
- }
- return r < 0;
- });
- } else {
- std::sort(entries.begin(), entries.end(),
- [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
- }
+void StringPool::Sort() {
+ Sort([](auto&&, auto&&) { return 0; });
}
-void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
+void StringPool::Sort(base::function_ref<int(const Context&, const Context&)> cmp) {
SortEntries(styles_, cmp);
SortEntries(strings_, cmp);
ReAssignIndices();
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 0190ab57bf23..9b2c72a29f48 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -17,7 +17,6 @@
#ifndef _ANDROID_STRING_POOL_H
#define _ANDROID_STRING_POOL_H
-#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
@@ -25,6 +24,7 @@
#include "BigBuffer.h"
#include "IDiagnostics.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
@@ -205,7 +205,8 @@ class StringPool {
// Sorts the strings according to their Context using some comparison function.
// Equal Contexts are further sorted by string value, lexicographically.
// If no comparison function is provided, values are only sorted lexicographically.
- void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr);
+ void Sort();
+ void Sort(base::function_ref<int(const Context&, const Context&)> cmp);
// Removes any strings that have no references.
void Prune();
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 341599e79662..e302fa8b1fc3 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -114,16 +114,12 @@ cc_defaults {
"libbase",
"libharfbuzz_ng",
"libminikin",
- "server_configurable_flags",
- "libaconfig_storage_read_api_cc"
],
static_libs: [
"libui-types",
],
- whole_static_libs: ["hwui_flags_cc_lib"],
-
target: {
android: {
shared_libs: [
@@ -145,6 +141,8 @@ cc_defaults {
"libsync",
"libui",
"aconfig_text_flags_c_lib",
+ "server_configurable_flags",
+ "libaconfig_storage_read_api_cc",
],
static_libs: [
"libEGL_blobCache",
@@ -155,6 +153,7 @@ cc_defaults {
"libstatssocket_lazy",
"libtonemap",
],
+ whole_static_libs: ["hwui_flags_cc_lib"],
},
host: {
static_libs: [
@@ -419,7 +418,6 @@ cc_defaults {
],
static_libs: [
- "libnativehelper_lazy",
"libziparchive_for_incfs",
],
@@ -446,6 +444,7 @@ cc_defaults {
],
static_libs: [
"libgif",
+ "libnativehelper_lazy",
"libstatslog_hwui",
"libstatspull_lazy",
"libstatssocket_lazy",
@@ -464,6 +463,7 @@ cc_defaults {
],
static_libs: [
"libandroidfw",
+ "libnativehelper_jvm",
],
},
},
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 70a9ef04d6f3..b4e6b7243ddc 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -28,6 +28,7 @@ using namespace std;
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
+extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -41,6 +42,7 @@ extern int register_android_graphics_Shader(JNIEnv* env);
extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
extern int register_android_graphics_YuvImage(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env);
namespace android {
@@ -51,7 +53,12 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
extern int register_android_graphics_PathIterator(JNIEnv* env);
@@ -72,6 +79,7 @@ extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
#define REG_JNI(name) { name }
struct RegJNIRec {
@@ -83,6 +91,8 @@ struct RegJNIRec {
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
{"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
+ {"android.graphics.BitmapRegionDecoder",
+ REG_JNI(register_android_graphics_BitmapRegionDecoder)},
{"android.graphics.ByteBufferStreamAdaptor",
REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
{"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
@@ -95,11 +105,20 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
{"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+ {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+ {"android.graphics.HardwareBufferRenderer",
+ REG_JNI(register_android_graphics_HardwareBufferRenderer)},
+ {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)},
+ {"android.graphics.HardwareRendererObserver",
+ REG_JNI(register_android_graphics_HardwareRendererObserver)},
{"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
{"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
{"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
+ {"android.graphics.Mesh", REG_JNI(register_android_graphics_Mesh)},
+ {"android.graphics.MeshSpecification",
+ REG_JNI(register_android_graphics_MeshSpecification)},
{"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
{"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
{"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
@@ -118,6 +137,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
{"android.graphics.animation.RenderNodeAnimator",
REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
+ {"android.graphics.drawable.AnimatedImageDrawable",
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable)},
{"android.graphics.drawable.AnimatedVectorDrawable",
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
{"android.graphics.drawable.VectorDrawable",
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index cfca48084d97..0efb2c81af01 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -17,7 +17,6 @@
#include <SkFontMetrics.h>
#include <SkRRect.h>
#include <SkTextBlob.h>
-#include <com_android_graphics_hwui_flags.h>
#include "../utils/Color.h"
#include "Canvas.h"
@@ -30,7 +29,19 @@
#include "hwui/PaintFilter.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
namespace flags = com::android::graphics::hwui::flags;
+#else
+namespace flags {
+constexpr bool high_contrast_text_luminance() {
+ return false;
+}
+constexpr bool high_contrast_text_small_text_rect() {
+ return false;
+}
+} // namespace flags
+#endif
namespace android {
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index ae9792df3d82..b943496ae9f7 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -126,7 +126,7 @@ static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
SkSafeUnref(meshSpec);
}
-static jlong getMeshSpecificationFinalizer() {
+static jlong getMeshSpecificationFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
}
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index e3cdee6e7034..3b1b86160f3a 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -135,7 +135,7 @@ static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, j
proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
}
-static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy));
}