Restore bounds when returning from maximized window bounds
Saves the tasks bounds before toggling it to stable bounds when window
is maximized. When restoring from maximized state, restores to the saved
bounds.
Bug: 323387092
Test: Manual testing
Change-Id: I237a3ffca4b80538eff9ced8d2abedce3e1cccb1
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 99a00b8..120d681 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.graphics.Rect
import android.graphics.Region
import android.util.ArrayMap
import android.util.ArraySet
@@ -55,6 +56,8 @@
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
// Track corner/caption regions of desktop tasks, used to determine gesture exclusion
private val desktopExclusionRegions = SparseArray<Region>()
+ // Track last bounds of task before toggled to stable bounds
+ private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -307,6 +310,7 @@
taskId
)
freeformTasksInZOrder.remove(taskId)
+ boundsBeforeMaximizeByTaskId.remove(taskId)
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
@@ -358,6 +362,20 @@
}
/**
+ * Removes and returns the bounds saved before maximizing the given task.
+ */
+ fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
+ return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
+ }
+
+ /**
+ * Saves the bounds of the given task before maximizing.
+ */
+ fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) {
+ boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ }
+
+ /**
* Check if display with id [displayId] has desktop tasks stashed
*/
fun isStashed(displayId: Int): Boolean {
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 c369061..e210ea7 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
@@ -120,7 +120,6 @@
private var visualIndicator: DesktopModeVisualIndicator? = null
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
-
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
@@ -570,7 +569,10 @@
}
}
- /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+ /**
+ * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
+ * if available or the default bounds otherwise.
+ */
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -578,11 +580,21 @@
displayLayout.getStableBounds(stableBounds)
val destinationBounds = Rect()
if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
- // The desktop task is currently occupying the whole stable bounds, toggle to the
- // default bounds.
- getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ // 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 taskBoundsBeforeMaximize =
+ desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
+ if (taskBoundsBeforeMaximize != null) {
+ destinationBounds.set(taskBoundsBeforeMaximize)
+ } else {
+ getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ }
} else {
- // Toggle to the stable bounds.
+ // 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)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 9f3a4d9..0c45d52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
@@ -406,6 +407,31 @@
assertThat(listener.stashedOnSecondaryDisplay).isTrue()
}
+ @Test
+ fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
+ val taskId = 1
+ repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
+ repo.removeFreeformTask(taskId)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+ }
+
+ @Test
+ fun saveBoundsBeforeMaximize_boundsSavedByTaskId() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMaximize(taskId, bounds)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isEqualTo(bounds)
+ }
+
+ @Test
+ fun removeBoundsBeforeMaximize_returnsNullAfterBoundsRemoved() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMaximize(taskId, bounds)
+ repo.removeBoundsBeforeMaximize(taskId)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
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 4fbf2bd..93a967e 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
@@ -95,9 +95,10 @@
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.kotlin.times
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -116,13 +117,14 @@
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
- @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+ @Mock lateinit var toggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@@ -154,6 +156,10 @@
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -179,7 +185,7 @@
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
- mToggleResizeDesktopTaskTransitionHandler,
+ toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
desktopModeLoggerTransitionObserver,
@@ -929,15 +935,74 @@
controller.enterSplit(DEFAULT_DISPLAY, false)
verify(splitScreenController).requestEnterSplitSelect(
- task2,
- any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds
+ task2,
+ any(),
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ task2.configuration.windowConfiguration.bounds
)
}
- private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createFreeformTask(displayId)
+ @Test
+ fun toggleBounds_togglesToStableBounds() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(task)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
+ .isEqualTo(STABLE_BOUNDS)
+ }
+
+ @Test
+ fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(task)
+ assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId))
+ .isEqualTo(bounds)
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
+ .isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
+ fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert last bounds before maximize removed after use
+ assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+ }
+
+ private fun setUpFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null
+ ): RunningTaskInfo {
+ val task = createFreeformTask(displayId, bounds)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
@@ -1004,6 +1069,18 @@
return arg.value
}
+ private fun getLatestToggleResizeDesktopTaskWct(): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ .startTransition(capture(arg))
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ }
+ return arg.value
+ }
+
private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
@@ -1042,6 +1119,7 @@
companion object {
const val SECOND_DISPLAY = 2
+ private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 2f6f320..52da7fb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.Rect
import android.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -31,13 +32,17 @@
/** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
@JvmStatic
@JvmOverloads
- fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ fun createFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null
+ ): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.setLastActiveTime(100)
+ .apply { bounds?.let { setBounds(it) }}
.build()
}