Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 69aee985 authored by dakinola's avatar dakinola
Browse files

Animate task surface when attempting to snap resize from currently snap resized bounds

Resolving an edge case where if a desktop window was already snap resized, and is attempted to be drag snap resized again, the transition handler would see that the task bounds don't change so nothing would happen. Now in this case, we instead animate the task surface going back to its original bounds (similar to non-resizable case, but with no toast shown)

Fix: 362618481
Test: atest DesktopTasksControllerTest
Test: atest DesktopModeWindowDecorViewModelTests
Test: manually trying to snap resize from to the same edge twice
Flag: EXEMPT bugfix
Change-Id: I8b85c6677fa542dd5462199e5b2cdea0f6587f55
parent a3c982cc
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -734,17 +734,33 @@ class DesktopTasksController(
     * Quick-resize to the right or left half of the stable bounds.
     *
     * @param taskInfo current task that is being snap-resized via dragging or maximize menu button
     * @param taskSurface the leash of the task being dragged
     * @param currentDragBounds current position of the task leash being dragged (or current task
     *                          bounds if being snapped resize via maximize menu button)
     * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
     */
    fun snapToHalfScreen(
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl,
        currentDragBounds: Rect,
        position: SnapPosition
    ) {
        val destinationBounds = getSnapBounds(taskInfo, position)
        if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
        if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) {
            // Handle the case where we attempt to snap resize when already snap resized: the task
            // position won't need to change but we want to animate the surface going back to the
            // snapped position from the "dragged-to-the-edge" position.
            if (destinationBounds != currentDragBounds) {
                returnToDragStartAnimator.start(
                    taskInfo.taskId,
                    taskSurface,
                    startBounds = currentDragBounds,
                    endBounds = destinationBounds,
                    isResizable = taskInfo.isResizeable
                )
            }
            return
        }

        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
@@ -774,13 +790,14 @@ class DesktopTasksController(
                taskInfo.taskId,
                taskSurface,
                startBounds = currentDragBounds,
                endBounds = dragStartBounds
                endBounds = dragStartBounds,
                isResizable = taskInfo.isResizeable,
            )
        } else {
            interactionJankMonitor.begin(
                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
            )
            snapToHalfScreen(taskInfo, currentDragBounds, position)
            snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
        }
    }

+14 −6
Original line number Diff line number Diff line
@@ -48,7 +48,13 @@ class ReturnToDragStartAnimator(
    }

    /** Builds new animator and starts animation of task leash reposition. */
    fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) {
    fun start(
        taskId: Int,
        taskSurface: SurfaceControl,
        startBounds: Rect,
        endBounds: Rect,
        isResizable: Boolean
    ) {
        val tx = transactionSupplier.get()

        boundsAnimator?.cancel()
@@ -81,11 +87,13 @@ class ReturnToDragStartAnimator(
                                .apply()
                            taskRepositionAnimationListener.onAnimationEnd(taskId)
                            boundsAnimator = null
                            if (!isResizable) {
                                Toast.makeText(
                                    context,
                                    R.string.desktop_mode_non_resizable_snap_text,
                                    Toast.LENGTH_SHORT
                                ).show()
                            }
                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
                        }
                    )
+3 −1
Original line number Diff line number Diff line
@@ -491,7 +491,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        } else {
            mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
                    Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
            mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
            mDesktopTasksController.snapToHalfScreen(
                    decoration.mTaskInfo,
                    decoration.mTaskSurface,
                    decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
                    left ? SnapPosition.LEFT : SnapPosition.RIGHT);
        }
+37 −4
Original line number Diff line number Diff line
@@ -123,12 +123,12 @@ import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
@@ -2859,7 +2859,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
  }

  @Test
  fun getSnapBounds_calculatesBoundsForResizable() {
  fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
    val bounds = Rect(100, 100, 300, 300)
    val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
      topActivityInfo = ActivityInfo().apply {
@@ -2874,12 +2874,44 @@ class DesktopTasksControllerTest : ShellTestCase() {
      STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
    )

    controller.snapToHalfScreen(task, currentDragBounds, SnapPosition.LEFT)
    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
    // Assert bounds set to stable bounds
    val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
    assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
  }

  @Test
  fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
    assumeTrue(ENABLE_SHELL_TRANSITIONS)
    // Set up task to already be in snapped-left bounds
    val bounds = Rect(
      STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
    )
    val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
      topActivityInfo = ActivityInfo().apply {
        screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
        configuration.windowConfiguration.appBounds = bounds
      }
      isResizeable = true
    }

    // Attempt to snap left again
    val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)

    // 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(bounds),
      eq(true)
    )
  }

  @Test
  @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
  fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() {
@@ -2911,7 +2943,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
      eq(task.taskId),
      eq(mockSurface),
      eq(currentDragBounds),
      eq(preDragBounds)
      eq(preDragBounds),
      eq(false)
    )
  }

+37 −12
Original line number Diff line number Diff line
@@ -113,7 +113,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
@@ -600,6 +600,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {

    @Test
    fun testOnDecorSnappedLeft_snapResizes() {
        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
        val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
                as ArgumentCaptor<Function0<Unit>>
        val decor = createOpenTaskDecoration(
@@ -610,8 +611,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
        onLeftSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController)
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
        verify(mockDesktopTasksController).snapToHalfScreen(
            eq(decor.mTaskInfo),
            taskSurfaceCaptor.capture(),
            eq(currentBounds),
            eq(SnapPosition.LEFT)
        )
        assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface)
    }

    @Test
@@ -632,6 +638,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
    @Test
    @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
    fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() {
        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
        val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
                as ArgumentCaptor<Function0<Unit>>
        val decor = createOpenTaskDecoration(
@@ -642,8 +649,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
        onLeftSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController)
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
        verify(mockDesktopTasksController).snapToHalfScreen(
            eq(decor.mTaskInfo),
            taskSurfaceCaptor.capture(),
            eq(currentBounds),
            eq(SnapPosition.LEFT)
        )
        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
    }

    @Test
@@ -660,12 +672,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        onLeftSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController, never())
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT))
        verify(mockToast).show()
    }

    @Test
    fun testOnDecorSnappedRight_snapResizes() {
        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
        val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
                as ArgumentCaptor<Function0<Unit>>
        val decor = createOpenTaskDecoration(
@@ -676,8 +689,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
        onRightSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController)
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
        verify(mockDesktopTasksController).snapToHalfScreen(
            eq(decor.mTaskInfo),
            taskSurfaceCaptor.capture(),
            eq(currentBounds),
            eq(SnapPosition.RIGHT)
        )
        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
    }

    @Test
@@ -698,6 +716,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
    @Test
    @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
    fun testOnSnapResizeRight_nonResizable_decorSnappedRight() {
        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
        val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
                as ArgumentCaptor<Function0<Unit>>
        val decor = createOpenTaskDecoration(
@@ -708,8 +727,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
        onRightSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController)
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
        verify(mockDesktopTasksController).snapToHalfScreen(
            eq(decor.mTaskInfo),
            taskSurfaceCaptor.capture(),
            eq(currentBounds),
            eq(SnapPosition.RIGHT)
        )
        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
    }

    @Test
@@ -726,7 +750,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
        onRightSnapClickListenerCaptor.value.invoke()

        verify(mockDesktopTasksController, never())
            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT))
        verify(mockToast).show()
    }

@@ -1033,6 +1057,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {

    private fun createOpenTaskDecoration(
        @WindowingMode windowingMode: Int,
        taskSurface: SurfaceControl = SurfaceControl(),
        onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
            forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
        onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
@@ -1051,7 +1076,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
            forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener>
    ): DesktopModeWindowDecoration {
        val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
        onTaskOpening(decor.mTaskInfo)
        onTaskOpening(decor.mTaskInfo, taskSurface)
        verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
        verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
        verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())