Loading core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -7143,6 +7143,10 @@ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. --> <bool name="config_enterDesktopByDefaultOnFreeformDisplay">false</bool> <!-- Whether a desktop window should be maximized when it's dragged to the top edge of the screen. --> <bool name="config_dragToMaximizeInDesktopMode">false</bool> <!-- Frame rate compatibility value for Wallpaper FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption --> <integer name="config_wallpaperFrameRateCompatibility">102</integer> Loading core/res/res/values/symbols.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5577,6 +5577,10 @@ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. --> <java-symbol type="bool" name="config_enterDesktopByDefaultOnFreeformDisplay" /> <!-- Whether a desktop window should be maximized when it's dragged to the top edge of the screen. --> <java-symbol type="bool" name="config_dragToMaximizeInDesktopMode" /> <!-- Frame rate compatibility value for Wallpaper --> <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" /> Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +21 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,15 @@ public class DesktopModeStatus { public static final String ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP = "persist.wm.debug.enter_desktop_by_default_on_freeform_display"; /** * Sysprop declaring whether to enable drag-to-maximize for desktop windows. * * <p>If it is not defined, then {@code R.integer.config_dragToMaximizeInDesktopMode} * is used. */ public static final String ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP = "persist.wm.debug.enable_drag_to_maximize"; /** * Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time. * Loading Loading @@ -230,6 +239,18 @@ public class DesktopModeStatus { R.bool.config_enterDesktopByDefaultOnFreeformDisplay)); } /** * Return {@code true} if a window should be maximized when it's dragged to the top edge of the * screen. */ public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) { if (!Flags.enableDragToMaximize()) { return false; } return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP, context.getResources().getBoolean(R.bool.config_dragToMaximizeInDesktopMode)); } /** Dumps DesktopModeStatus flags and configs. */ public static void dump(PrintWriter pw, String prefix, Context context) { String innerPrefix = prefix + " "; Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +42 −5 Original line number Diff line number Diff line Loading @@ -857,6 +857,39 @@ class DesktopTasksController( toggleResizeDesktopTaskTransitionHandler.startTransition(wct) } private fun dragToMaximizeDesktopTask( taskInfo: RunningTaskInfo, taskSurface: SurfaceControl, currentDragBounds: Rect, motionEvent: MotionEvent ) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) if (isTaskMaximized(taskInfo, stableBounds)) { // Handle the case where we attempt to drag-to-maximize when already maximized: the task // position won't need to change but we want to animate the surface going back to the // maximized position. val containerBounds = taskInfo.configuration.windowConfiguration.bounds if (containerBounds != currentDragBounds) { returnToDragStartAnimator.start( taskInfo.taskId, taskSurface, startBounds = currentDragBounds, endBounds = containerBounds, isResizable = taskInfo.isResizeable ) } return } // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top. desktopModeEventLogger.logTaskResizingStarted( ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController ) toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent) } private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect { if (taskInfo.isResizeable) { // if resizable then expand to entire stable bounds (full display minus insets) Loading Loading @@ -1918,12 +1951,16 @@ class DesktopTasksController( ) when (indicatorType) { IndicatorType.TO_FULLSCREEN_INDICATOR -> { if (DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)) { dragToMaximizeDesktopTask(taskInfo, taskSurface, currentDragBounds, motionEvent) } else { moveToFullscreenWithAnimation( taskInfo, position, DesktopModeTransitionSource.TASK_DRAG ) } } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { handleSnapResizingTask( taskInfo, Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +117 −0 Original line number Diff line number Diff line Loading @@ -2913,6 +2913,123 @@ class DesktopTasksControllerTest : ShellTestCase() { eq(null)) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ Rect(100, 50, 500, 1000), /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert the task exits desktop mode val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ Rect(100, 50, 500, 1000), /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() { val task = setUpFreeformTask(bounds = STABLE_BOUNDS) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge val currentDragBounds = Rect(100, 50, 500, 1000) spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ currentDragBounds, /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert that task is NOT updated via WCT verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) // Assert that task leash is updated via Surface Animations verify(mReturnToDragStartAnimator).start( eq(task.taskId), eq(mockSurface), eq(currentDragBounds), eq(STABLE_BOUNDS), eq(true) ) } @Test fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() Loading Loading
core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -7143,6 +7143,10 @@ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. --> <bool name="config_enterDesktopByDefaultOnFreeformDisplay">false</bool> <!-- Whether a desktop window should be maximized when it's dragged to the top edge of the screen. --> <bool name="config_dragToMaximizeInDesktopMode">false</bool> <!-- Frame rate compatibility value for Wallpaper FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption --> <integer name="config_wallpaperFrameRateCompatibility">102</integer> Loading
core/res/res/values/symbols.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5577,6 +5577,10 @@ root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. --> <java-symbol type="bool" name="config_enterDesktopByDefaultOnFreeformDisplay" /> <!-- Whether a desktop window should be maximized when it's dragged to the top edge of the screen. --> <java-symbol type="bool" name="config_dragToMaximizeInDesktopMode" /> <!-- Frame rate compatibility value for Wallpaper --> <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" /> Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +21 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,15 @@ public class DesktopModeStatus { public static final String ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP = "persist.wm.debug.enter_desktop_by_default_on_freeform_display"; /** * Sysprop declaring whether to enable drag-to-maximize for desktop windows. * * <p>If it is not defined, then {@code R.integer.config_dragToMaximizeInDesktopMode} * is used. */ public static final String ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP = "persist.wm.debug.enable_drag_to_maximize"; /** * Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time. * Loading Loading @@ -230,6 +239,18 @@ public class DesktopModeStatus { R.bool.config_enterDesktopByDefaultOnFreeformDisplay)); } /** * Return {@code true} if a window should be maximized when it's dragged to the top edge of the * screen. */ public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) { if (!Flags.enableDragToMaximize()) { return false; } return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP, context.getResources().getBoolean(R.bool.config_dragToMaximizeInDesktopMode)); } /** Dumps DesktopModeStatus flags and configs. */ public static void dump(PrintWriter pw, String prefix, Context context) { String innerPrefix = prefix + " "; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +42 −5 Original line number Diff line number Diff line Loading @@ -857,6 +857,39 @@ class DesktopTasksController( toggleResizeDesktopTaskTransitionHandler.startTransition(wct) } private fun dragToMaximizeDesktopTask( taskInfo: RunningTaskInfo, taskSurface: SurfaceControl, currentDragBounds: Rect, motionEvent: MotionEvent ) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) if (isTaskMaximized(taskInfo, stableBounds)) { // Handle the case where we attempt to drag-to-maximize when already maximized: the task // position won't need to change but we want to animate the surface going back to the // maximized position. val containerBounds = taskInfo.configuration.windowConfiguration.bounds if (containerBounds != currentDragBounds) { returnToDragStartAnimator.start( taskInfo.taskId, taskSurface, startBounds = currentDragBounds, endBounds = containerBounds, isResizable = taskInfo.isResizeable ) } return } // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top. desktopModeEventLogger.logTaskResizingStarted( ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController ) toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent) } private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect { if (taskInfo.isResizeable) { // if resizable then expand to entire stable bounds (full display minus insets) Loading Loading @@ -1918,12 +1951,16 @@ class DesktopTasksController( ) when (indicatorType) { IndicatorType.TO_FULLSCREEN_INDICATOR -> { if (DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)) { dragToMaximizeDesktopTask(taskInfo, taskSurface, currentDragBounds, motionEvent) } else { moveToFullscreenWithAnimation( taskInfo, position, DesktopModeTransitionSource.TASK_DRAG ) } } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { handleSnapResizingTask( taskInfo, Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +117 −0 Original line number Diff line number Diff line Loading @@ -2913,6 +2913,123 @@ class DesktopTasksControllerTest : ShellTestCase() { eq(null)) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ Rect(100, 50, 500, 1000), /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert the task exits desktop mode val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() { val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ Rect(100, 50, 500, 1000), /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @Test fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() { val task = setUpFreeformTask(bounds = STABLE_BOUNDS) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge val currentDragBounds = Rect(100, 50, 500, 1000) spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ currentDragBounds, /* currentDragBounds */ Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert that task is NOT updated via WCT verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) // Assert that task leash is updated via Surface Animations verify(mReturnToDragStartAnimator).start( eq(task.taskId), eq(mockSurface), eq(currentDragBounds), eq(STABLE_BOUNDS), eq(true) ) } @Test fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() Loading