Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ enum class DesktopModeFlags( ) { // All desktop mode related flags will be added here DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true), CASCADING_WINDOWS(Flags::enableCascadingWindows, true), WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true), MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +9 −13 Original line number Diff line number Diff line Loading @@ -52,8 +52,10 @@ fun calculateInitialBounds( val idealSize = calculateIdealSize(screenBounds, scale) // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated. // Instead default to the desired initial bounds. val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) val topActivityInfo = taskInfo.topActivityInfo ?: return positionInScreen(idealSize, screenBounds) taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds) val initialSize: Size = when (taskInfo.configuration.orientation) { Loading Loading @@ -100,7 +102,7 @@ fun calculateInitialBounds( } } return positionInScreen(initialSize, screenBounds) return positionInScreen(initialSize, stableBounds) } /** Loading Loading @@ -163,16 +165,10 @@ private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size { } /** Adjusts bounds to be positioned in the middle of the screen. */ private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect { // TODO(b/325240051): Position apps with bottom heavy offset val heightOffset = (screenBounds.height() - desiredSize.height) / 2 val widthOffset = (screenBounds.width() - desiredSize.width) / 2 return Rect( widthOffset, heightOffset, desiredSize.width + widthOffset, desiredSize.height + heightOffset ) private fun positionInScreen(desiredSize: Size, stableBounds: Rect): Rect = Rect(0, 0, desiredSize.width, desiredSize.height).apply { val offset = DesktopTaskPosition.Center.getTopLeftCoordinates(stableBounds, this) offsetTo(offset.x, offset.y) } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.desktopmode import android.app.TaskInfo import android.graphics.Point import android.graphics.Rect import android.view.Gravity import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight /** * The position of a task window in desktop mode. */ sealed class DesktopTaskPosition { data object Center : DesktopTaskPosition() { private const val WINDOW_HEIGHT_PROPORTION = 0.375 override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { val x = (frame.width() - window.width()) / 2 // Position with more margin at the bottom. val y = (frame.height() - window.height()) * WINDOW_HEIGHT_PROPORTION + frame.top return Point(x, y.toInt()) } override fun next(): DesktopTaskPosition { return BottomRight } } data object BottomRight : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.right - window.width(), frame.bottom - window.height()) } override fun next(): DesktopTaskPosition { return TopLeft } } data object TopLeft : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.left, frame.top) } override fun next(): DesktopTaskPosition { return BottomLeft } } data object BottomLeft : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.left, frame.bottom - window.height()) } override fun next(): DesktopTaskPosition { return TopRight } } data object TopRight : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.right - window.width(), frame.top) } override fun next(): DesktopTaskPosition { return Center } } /** * Returns the top left coordinates for the window to be placed in the given * DesktopTaskPosition in the frame. */ abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point abstract fun next(): DesktopTaskPosition } /** * If the app has specified horizontal or vertical gravity layout, don't change the * task position for cascading effect. */ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean { taskInfo.topActivityInfo?.windowLayout?.let { val horizontalGravityApplied = it.gravity.and(Gravity.HORIZONTAL_GRAVITY_MASK) val verticalGravityApplied = it.gravity.and(Gravity.VERTICAL_GRAVITY_MASK) return horizontalGravityApplied == 0 && verticalGravityApplied == 0 } return true } /** * Returns the current DesktopTaskPosition for a given window in the frame. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition { return when { top == bounds.top && left == bounds.left -> TopLeft top == bounds.top && right == bounds.right -> TopRight bottom == bounds.bottom && left == bounds.left -> BottomLeft bottom == bounds.bottom && right == bounds.right -> BottomRight else -> Center } } internal fun cascadeWindow(frame: Rect, prev: Rect, dest: Rect) { val lastPos = frame.getDesktopTaskPosition(prev) val candidatePos = lastPos.next() val destCoord = candidatePos.getTopLeftCoordinates(frame, dest) dest.offsetTo(destCoord.x, destCoord.y) } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +27 −10 Original line number Diff line number Diff line Loading @@ -403,7 +403,7 @@ class DesktopTasksController( * The second part of the animated drag to desktop transition, called after * [startDragToDesktop]. */ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d", Loading @@ -415,7 +415,6 @@ class DesktopTasksController( val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) transition?.let { addPendingMinimizeTransition(it, taskToMinimize) } } Loading Loading @@ -1071,10 +1070,12 @@ class DesktopTasksController( return if (wct.isEmpty) null else wct } private fun addMoveToDesktopChanges( @VisibleForTesting fun addMoveToDesktopChanges( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = Loading @@ -1084,6 +1085,28 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } val initialBounds = if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { calculateInitialBounds(displayLayout, taskInfo) } else { getDefaultDesktopTaskBounds(displayLayout) } if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) { val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) val activeTasks = desktopModeTaskRepository .getActiveNonMinimizedOrderedTasks(taskInfo.displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { cascadeWindow(stableBounds, it.configuration.windowConfiguration.bounds, initialBounds) } } } if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) } wct.setWindowingMode(taskInfo.token, targetWindowingMode) wct.reorder(taskInfo.token, true /* onTop */) if (useDesktopOverrideDensity()) { Loading Loading @@ -1343,16 +1366,10 @@ class DesktopTasksController( val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return IndicatorType.NO_INDICATOR // Start a new jank interaction for the drag release to desktop window animation. interactionJankMonitor.begin(taskSurface, context, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) } else { finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout)) } finalizeDragToDesktop(taskInfo) } IndicatorType.NO_INDICATOR, IndicatorType.TO_FULLSCREEN_INDICATOR -> { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +168 −7 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.Gravity import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE Loading Loading @@ -185,12 +186,12 @@ class DesktopTasksControllerTest : ShellTestCase() { private val DISPLAY_DIMENSION_SHORT = 1600 private val DISPLAY_DIMENSION_LONG = 2560 private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400) private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240) private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880) private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400) private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861) private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400) private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085) private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635) private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275) private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611) private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275) @Before fun setUp() { Loading Loading @@ -589,6 +590,140 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.LEFT) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.RIGHT) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.TOP) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionBottomRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.BottomRight) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionTopLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.TopLeft) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionBottomLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.BottomLeft) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionTopRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.TopRight) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.Center) } @Test fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() Loading Loading @@ -2406,6 +2541,17 @@ class DesktopTasksControllerTest : ShellTestCase() { private val desktopWallpaperIntent: Intent get() = Intent(context, DesktopWallpaperActivity::class.java) private fun addFreeformTaskAtPosition( pos: DesktopTaskPosition, stableBounds: Rect, bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS ): RunningTaskInfo { val offset = pos.getTopLeftCoordinates(stableBounds, bounds) val prevTaskBounds = Rect(bounds) prevTaskBounds.offsetTo(offset.x, offset.y) return setUpFreeformTask(bounds = prevTaskBounds) } private fun setUpFreeformTask( displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null Loading Loading @@ -2434,11 +2580,13 @@ class DesktopTasksControllerTest : ShellTestCase() { windowingMode: Int = WINDOWING_MODE_FULLSCREEN, deviceOrientation: Int = ORIENTATION_LANDSCAPE, screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, shouldLetterbox: Boolean = false shouldLetterbox: Boolean = false, gravity: Int = Gravity.NO_GRAVITY ): RunningTaskInfo { val task = createFullscreenTask(displayId) val activityInfo = ActivityInfo() activityInfo.screenOrientation = screenOrientation activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0) with(task) { topActivityInfo = activityInfo isResizeable = isResizable Loading Loading @@ -2479,11 +2627,23 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpLandscapeDisplay() { whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT ) whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(stableBounds) } } private fun setUpPortraitDisplay() { whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT ) whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(stableBounds) } } private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { Loading Loading @@ -2601,6 +2761,7 @@ class DesktopTasksControllerTest : ShellTestCase() { const val SECOND_DISPLAY = 2 val STABLE_BOUNDS = Rect(0, 0, 1000, 1000) const val MAX_TASK_LIMIT = 6 private const val TASKBAR_FRAME_HEIGHT = 200 } } Loading Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ enum class DesktopModeFlags( ) { // All desktop mode related flags will be added here DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true), CASCADING_WINDOWS(Flags::enableCascadingWindows, true), WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true), MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +9 −13 Original line number Diff line number Diff line Loading @@ -52,8 +52,10 @@ fun calculateInitialBounds( val idealSize = calculateIdealSize(screenBounds, scale) // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated. // Instead default to the desired initial bounds. val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) val topActivityInfo = taskInfo.topActivityInfo ?: return positionInScreen(idealSize, screenBounds) taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds) val initialSize: Size = when (taskInfo.configuration.orientation) { Loading Loading @@ -100,7 +102,7 @@ fun calculateInitialBounds( } } return positionInScreen(initialSize, screenBounds) return positionInScreen(initialSize, stableBounds) } /** Loading Loading @@ -163,16 +165,10 @@ private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size { } /** Adjusts bounds to be positioned in the middle of the screen. */ private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect { // TODO(b/325240051): Position apps with bottom heavy offset val heightOffset = (screenBounds.height() - desiredSize.height) / 2 val widthOffset = (screenBounds.width() - desiredSize.width) / 2 return Rect( widthOffset, heightOffset, desiredSize.width + widthOffset, desiredSize.height + heightOffset ) private fun positionInScreen(desiredSize: Size, stableBounds: Rect): Rect = Rect(0, 0, desiredSize.width, desiredSize.height).apply { val offset = DesktopTaskPosition.Center.getTopLeftCoordinates(stableBounds, this) offsetTo(offset.x, offset.y) } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.desktopmode import android.app.TaskInfo import android.graphics.Point import android.graphics.Rect import android.view.Gravity import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight /** * The position of a task window in desktop mode. */ sealed class DesktopTaskPosition { data object Center : DesktopTaskPosition() { private const val WINDOW_HEIGHT_PROPORTION = 0.375 override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { val x = (frame.width() - window.width()) / 2 // Position with more margin at the bottom. val y = (frame.height() - window.height()) * WINDOW_HEIGHT_PROPORTION + frame.top return Point(x, y.toInt()) } override fun next(): DesktopTaskPosition { return BottomRight } } data object BottomRight : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.right - window.width(), frame.bottom - window.height()) } override fun next(): DesktopTaskPosition { return TopLeft } } data object TopLeft : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.left, frame.top) } override fun next(): DesktopTaskPosition { return BottomLeft } } data object BottomLeft : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.left, frame.bottom - window.height()) } override fun next(): DesktopTaskPosition { return TopRight } } data object TopRight : DesktopTaskPosition() { override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point { return Point(frame.right - window.width(), frame.top) } override fun next(): DesktopTaskPosition { return Center } } /** * Returns the top left coordinates for the window to be placed in the given * DesktopTaskPosition in the frame. */ abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point abstract fun next(): DesktopTaskPosition } /** * If the app has specified horizontal or vertical gravity layout, don't change the * task position for cascading effect. */ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean { taskInfo.topActivityInfo?.windowLayout?.let { val horizontalGravityApplied = it.gravity.and(Gravity.HORIZONTAL_GRAVITY_MASK) val verticalGravityApplied = it.gravity.and(Gravity.VERTICAL_GRAVITY_MASK) return horizontalGravityApplied == 0 && verticalGravityApplied == 0 } return true } /** * Returns the current DesktopTaskPosition for a given window in the frame. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition { return when { top == bounds.top && left == bounds.left -> TopLeft top == bounds.top && right == bounds.right -> TopRight bottom == bounds.bottom && left == bounds.left -> BottomLeft bottom == bounds.bottom && right == bounds.right -> BottomRight else -> Center } } internal fun cascadeWindow(frame: Rect, prev: Rect, dest: Rect) { val lastPos = frame.getDesktopTaskPosition(prev) val candidatePos = lastPos.next() val destCoord = candidatePos.getTopLeftCoordinates(frame, dest) dest.offsetTo(destCoord.x, destCoord.y) }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +27 −10 Original line number Diff line number Diff line Loading @@ -403,7 +403,7 @@ class DesktopTasksController( * The second part of the animated drag to desktop transition, called after * [startDragToDesktop]. */ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d", Loading @@ -415,7 +415,6 @@ class DesktopTasksController( val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) transition?.let { addPendingMinimizeTransition(it, taskToMinimize) } } Loading Loading @@ -1071,10 +1070,12 @@ class DesktopTasksController( return if (wct.isEmpty) null else wct } private fun addMoveToDesktopChanges( @VisibleForTesting fun addMoveToDesktopChanges( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = Loading @@ -1084,6 +1085,28 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } val initialBounds = if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { calculateInitialBounds(displayLayout, taskInfo) } else { getDefaultDesktopTaskBounds(displayLayout) } if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) { val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) val activeTasks = desktopModeTaskRepository .getActiveNonMinimizedOrderedTasks(taskInfo.displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { cascadeWindow(stableBounds, it.configuration.windowConfiguration.bounds, initialBounds) } } } if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) } wct.setWindowingMode(taskInfo.token, targetWindowingMode) wct.reorder(taskInfo.token, true /* onTop */) if (useDesktopOverrideDensity()) { Loading Loading @@ -1343,16 +1366,10 @@ class DesktopTasksController( val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return IndicatorType.NO_INDICATOR // Start a new jank interaction for the drag release to desktop window animation. interactionJankMonitor.begin(taskSurface, context, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) } else { finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout)) } finalizeDragToDesktop(taskInfo) } IndicatorType.NO_INDICATOR, IndicatorType.TO_FULLSCREEN_INDICATOR -> { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +168 −7 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.Gravity import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE Loading Loading @@ -185,12 +186,12 @@ class DesktopTasksControllerTest : ShellTestCase() { private val DISPLAY_DIMENSION_SHORT = 1600 private val DISPLAY_DIMENSION_LONG = 2560 private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400) private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240) private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880) private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400) private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861) private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400) private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085) private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635) private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275) private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611) private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275) @Before fun setUp() { Loading Loading @@ -589,6 +590,140 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.LEFT) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.RIGHT) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.TOP) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionBottomRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.BottomRight) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionTopLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.TopLeft) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionBottomLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.BottomLeft) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionTopRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.TopRight) } @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) fun addMoveToDesktopChanges_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds) val task = setUpFullscreenTask() val wct = WindowContainerTransaction() controller.addMoveToDesktopChanges(wct, task) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) .isEqualTo(DesktopTaskPosition.Center) } @Test fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() Loading Loading @@ -2406,6 +2541,17 @@ class DesktopTasksControllerTest : ShellTestCase() { private val desktopWallpaperIntent: Intent get() = Intent(context, DesktopWallpaperActivity::class.java) private fun addFreeformTaskAtPosition( pos: DesktopTaskPosition, stableBounds: Rect, bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS ): RunningTaskInfo { val offset = pos.getTopLeftCoordinates(stableBounds, bounds) val prevTaskBounds = Rect(bounds) prevTaskBounds.offsetTo(offset.x, offset.y) return setUpFreeformTask(bounds = prevTaskBounds) } private fun setUpFreeformTask( displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null Loading Loading @@ -2434,11 +2580,13 @@ class DesktopTasksControllerTest : ShellTestCase() { windowingMode: Int = WINDOWING_MODE_FULLSCREEN, deviceOrientation: Int = ORIENTATION_LANDSCAPE, screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, shouldLetterbox: Boolean = false shouldLetterbox: Boolean = false, gravity: Int = Gravity.NO_GRAVITY ): RunningTaskInfo { val task = createFullscreenTask(displayId) val activityInfo = ActivityInfo() activityInfo.screenOrientation = screenOrientation activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0) with(task) { topActivityInfo = activityInfo isResizeable = isResizable Loading Loading @@ -2479,11 +2627,23 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpLandscapeDisplay() { whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT ) whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(stableBounds) } } private fun setUpPortraitDisplay() { whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT ) whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(stableBounds) } } private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { Loading Loading @@ -2601,6 +2761,7 @@ class DesktopTasksControllerTest : ShellTestCase() { const val SECOND_DISPLAY = 2 val STABLE_BOUNDS = Rect(0, 0, 1000, 1000) const val MAX_TASK_LIMIT = 6 private const val TASKBAR_FRAME_HEIGHT = 200 } } Loading