Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +44 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) } } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +29 −9 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -649,13 +650,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) { Loading @@ -670,9 +679,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) 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) Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +60 −0 Original line number Diff line number Diff line Loading @@ -1915,6 +1915,26 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @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) Loading @@ -1941,6 +1961,46 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) } @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) Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +44 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) } }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +29 −9 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -649,13 +650,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) { Loading @@ -670,9 +679,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) 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) Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +60 −0 Original line number Diff line number Diff line Loading @@ -1915,6 +1915,26 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @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) Loading @@ -1941,6 +1961,46 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) } @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) Loading